root/gaphor/tags/gaphor-0.9.2/gaphor/pluginmanager.py

Revision 1135, 12.9 kB (checked in by arjanmol, 2 years ago)

cleanup

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 Plugin Manager.
3
4 The plugin manager is used to manage (register/unregister) plugins.
5
6 A Plugin is presented as a directory. This directory contains two files:
7  - plugin.xml contains a description of the plugin
8  - __init__.py is needed since the plugin is loaded as a module
9  
10 The following classes are provided by this module:
11
12 PluginManager - The main class. PluginManager.bootstrap() loads all plugins
13         from the default plugin dirs ($PREFIX/share/gaphor/plugins and
14         $HOME/.gaphor/plugins).
15 Plugin - This class represents a plugin. It contains data from plugin.xml
16         as well as data gathered during the plugin loading process (such as
17         the module that was created when the plugin is imported.
18 PluginAction - Defines an gaphor.plugin.Action instance.
19 PluginLoader - SAX parser used to read the plugin.xml file of a plugin. This
20         class creates a Plugin instance from the xml file.
21
22 User provided plugins overrule the system provided plugins.
23 """
24 import os
25 import imp
26 import os.path
27 import glob
28 import sys
29 from xml.sax import handler, make_parser
30 from gaphor import resource
31
32 from gaphor.parser import ParserException
33 from gaphor.misc.action import register_action_for_slot
34 from gaphor.misc.odict import odict
35
36 XMLNS='http://gaphor.sourceforge.net/gaphor/plugin'
37 MODULENS='gaphor._plugins.'
38
39 # Directories to look for plugins. These sirectories are added to the
40 # search path. User provided plugins overrule system plugins.
41 DEFAULT_PLUGIN_DIRS = [os.path.join(resource('DataDir'), 'plugins'),
42                        os.path.join(resource('UserDataDir'), 'plugins')]
43
44 #log.debug('sys.path=' + str(sys.path))
45 #log.debug('DEFAULT_PLUGIN_DIRS=' + str(DEFAULT_PLUGIN_DIRS))
46
47 class Plugin(object):
48     """
49     A plugin represents one plugin loaded from the file system.
50     """
51
52     def __init__(self):
53         self.required_modules = []
54         self.required_plugins = []
55         self.required_actions = []
56         self.provided_actions = []
57         self.description = ''
58         self.initialized = False
59         self.path = ''
60         self.module = None
61         self.status = ''
62
63     def requirements_met(self, manager):
64         """
65         Check if all <require>-ments are met to load the plugin.
66         """
67         if self.initialized:
68             return False
69
70         for mod in self.required_modules:
71             try:
72                 # TODO: How to find out if a module exists without loading it?
73                 __import__(mod, globals(), locals(), [])
74             except ImportError:
75                 self.initialized = True
76                 self.status = 'Could not initialize plugin: required module %s could not be imported' % mod
77                 log.debug(self.status)
78                 return False
79
80         pluginstatus = {}
81         for p in manager.plugins.itervalues():
82             pluginstatus[p.name] = bool(p.initialized)
83         for p in self.required_plugins:
84             if not pluginstatus.get(p):
85                 self.status = 'Plugin %s is a prerequisite' % p
86                 log.debug(self.status + ' for %s' % self.name)
87                 return False
88
89         return True
90
91     def import_plugin(self):
92         """
93         Do the actual import of the plugin module.
94         """
95         #mod = __import__(self.path.split(os.sep)[-1], globals(), locals(), [])
96         name = os.path.split(self.path)[1]
97         f, n, d = imp.find_module(name, DEFAULT_PLUGIN_DIRS)
98         mod = imp.load_module(MODULENS + name, f, n, d)
99         self.module = mod
100         self.initialized = True
101         if mod:
102             self.status = 'Imported'
103
104         #log.debug('Trying to import %d actions' % len(self.provided_actions))
105         for action in self.provided_actions:
106             try:
107                 #log.debug('Trying to import action %s' % action.id)
108                 action.import_action(self)
109             except Exception, e:
110                 self.status += '\nFailed to import action %s (%s)' % (action.id or action.class_, e)
111                 log.error('Failed to import action %s' % (action.id or action.class_), e)
112
113
114 class PluginAction(object):
115     """
116     Each action within a Plugin is represented by a PluginAction.
117     """
118
119     def __init__(self, class_, slot):
120         self.class_ = class_
121         self.slot = slot
122         self.id = None
123         self.stock_id = None
124         self.icon_file = None
125         self.tooltip = None
126         self.label = None
127         self.accel = None
128         self.depends = []
129
130     def import_action(self, plugin):
131         """
132         Import and register one action in the plugin.
133         """
134         # Create an icon for the plugin
135         if self.icon_file:
136             from gaphor.ui.stock import add_stock_icon
137             self.stock_id = 'gaphor-plugin-' + self.id
138             add_stock_icon(self.stock_id, plugin.path, [self.icon_file])
139
140         # Fetch the action class
141         action_class = getattr(plugin.module, self.class_)
142
143         # Copy attributes from the plugin to the class
144         for attr in ('id', 'label', 'stock_id', 'tooltip', 'accel'):
145             val = getattr(self, attr, None)
146             if val:
147                 setattr(action_class, attr, val)
148
149         register_action_for_slot(action_class, self.slot, *self.depends)
150         log.debug('Providing action %s for slot %s' % (action_class, self.slot))
151
152 class PluginLoader(handler.ContentHandler):
153     """
154     Load a Plugin definition file (plugin.xml).
155     """
156
157     # A typical plugin.xml file might look like this:
158     # <?xml version="1.0"?>                            ## mode => TOPLEVEL
159     # <gaphor-plugin name="UML metamodel sanity check" ## mode => PLUGIN
160     #         version="0.1"
161     #         author="Arjan Molenaar">
162     #   <description>                                  ## mode => DESCRIPTION
163     #     ...
164     #   </description>                                 ## mode <= PLUGIN
165     #
166     #   <require>                                      ## mode => REQUIRE
167     #     <module name="os.path"/>
168     #     <plugin name="Test plugin"/>
169     #   </require>                                     ## mode <= PLUGIN
170     #
171     #   <provide>                                      ## mode => PROVIDE
172     #     <action id="CheckMetamodel"                  ## mode => ACTION
173     #             label="Check UML metamodel"
174     #             tooltip="Check the UML meta model for errors"
175     #             class="CheckMetamodelAction" slot="WindowSlot">
176     #       <depends action=""/>
177     #     </action>                                    ## mode <= PROVIDE
178     #
179     #   </provide>                                     ## mode <= PLUGIN
180     # </gaphor-plugin>                                 ## mode <= TOPLEVEL
181
182     TOPLEVEL = 0
183     PLUGIN = 1
184     REQUIRE = 2
185     PROVIDE = 3
186     ACTION = 4
187     DESCRIPTION = 5
188
189     def __init__(self):
190         handler.ContentHandler.__init__(self)
191
192     def endDTD(self):
193         pass
194
195     def startDocument(self):
196         """
197         Start of document: all our attributes are initialized.
198         """
199         self.plugin = Plugin()
200         self.mode = self.TOPLEVEL
201
202     def endDocument(self):
203         assert self.mode == self.TOPLEVEL
204
205     def startElement(self, name, attrs):
206         mode = self.mode
207         if mode == self.TOPLEVEL:
208             if name == 'gaphor-plugin':
209                 self.plugin.name = attrs['name']
210                 self.plugin.version = attrs['version']
211                 self.plugin.author = attrs['author']
212                 self.mode = self.PLUGIN
213             else:
214                 raise ParserException, 'Invalid XML: expecting <gaphor-plugin>. Got <%s>.' % name
215
216         elif mode == self.PLUGIN:
217             if name == 'description':
218                 self.mode = self.DESCRIPTION
219             elif name == 'require':
220                 self.mode = self.REQUIRE
221             elif name == 'provide':
222                 self.mode = self.PROVIDE
223             else:
224                 raise ParserException, 'Invalid XML: expecting <description>, <require> or <provide>. Got <%s>.' % name
225
226         elif mode == self.REQUIRE:
227             if name == 'module':
228                 self.plugin.required_modules.append(attrs['name'])
229             elif name == 'action':
230                 self.plugin.required_actions.append(attrs['name'])
231             elif name == 'plugin':
232                 self.plugin.required_plugins.append(attrs['name'])
233             else:
234                 raise ParserException, 'Invalid XML: expecting <module>, <action> or <plugin>. Got <%s>.' % name
235
236         elif mode == self.PROVIDE:
237             if name == 'action':
238                 action = PluginAction(attrs['class'].encode(),
239                                           attrs['slot'].encode())
240                 action.id = attrs.get('id', '').encode()
241                 action.stock_id = attrs.get('stock-id', '').encode()
242                 action.icon_file = attrs.get('icon-file', '').encode()
243                 action.tooltip = attrs.get('tooltip', '').encode()
244                 action.label = attrs.get('label', '').encode()
245                 action.accel = attrs.get('accel', '').encode()
246                 self.plugin.provided_actions.append(action)
247                 self.mode = self.ACTION
248             else:
249                 raise ParserException, 'Invalid XML: expecting <action>. Got <%s>.' % name
250
251         elif mode == self.ACTION:
252             if name == 'depends':
253                 self.plugin.provided_actions[-1].depends.append(attrs['action'].encode())
254             else:
255                 raise ParserException, 'Invalid XML: expecting <depends>. Got <%s>.' % name
256
257         else:
258             raise ParserException, 'Invalid XML: tag <%s> not known' % name
259
260     def endElement(self, name):
261         if self.mode in (self.REQUIRE, self.PROVIDE, self.DESCRIPTION) and \
262            name in ('description', 'require', 'provide'):
263             self.mode = self.PLUGIN
264         elif self.mode == self.ACTION and name == 'action':
265             self.mode = self.PROVIDE
266         elif self.mode == self.PLUGIN and name == 'gaphor-plugin':
267             self.mode = self.TOPLEVEL
268         elif name in ('plugin', 'module', 'depends'):
269             pass
270         else:
271             raise ParserException, 'Invalid XML: tag <%s> not known' % name
272
273     def startElementNS(self, name, qname, attrs):
274         if not name[0] or name[0] == XMLNS:
275             a = { }
276             for key, val in attrs.items():
277                 a[key[1]] = val
278             self.startElement(name[1], a)
279
280     def endElementNS(self, name, qname):
281         if not name[0] or name[0] == XMLNS:
282             self.endElement(name[1])
283
284     def characters(self, content):
285         """Read characters."""
286         if self.mode == self.DESCRIPTION:
287             self.plugin.description += content
288
289
290 class PluginManager(object):
291     """
292     The PluginManager is the main point where plugins are managed.
293     """
294
295     def __init__(self):
296         self.plugins = odict()
297         self.bootstrapped = False
298
299         # Create an XML parser
300         self.parser = make_parser()
301         self.loader = PluginLoader()
302         self.parser.setFeature(handler.feature_namespaces, 1)
303         self.parser.setContentHandler(self.loader)
304
305     def bootstrap(self):
306         """
307         Do the normal plugin loading.
308         """
309         if self.bootstrapped:
310             return
311
312         # Load the plugins in reverse order, so the user plugins will
313         # overwrite the default plugins. (they are imported in sys.path as
314         # [user plugins, default plugins]).
315         for plugin_dir in DEFAULT_PLUGIN_DIRS:
316             self.load_plugins_from_dir(plugin_dir)
317
318         import_done = True
319         while import_done:
320             import_done = False
321             for plugin in self.plugins.itervalues():
322                 if plugin.requirements_met(self):
323                     try:
324                         plugin.import_plugin()
325                     except Exception, e:
326                         plugin.status = 'Failed to load plugin %s: %s' % (plugin.name, e)
327                         log.error('Failed to load plugin %s' % plugin.name, e)
328                     else:
329                         import_done = True
330
331         self.bootstrapped = True
332
333     def load_plugin(self, plugin_xml):
334         """
335         Load a plugin definition and store the plugin in the manager.
336         """
337         assert not self.plugins.has_key(os.path.dirname(plugin_xml))
338         log.debug('Loading definitions from %s' % plugin_xml)
339         self.parser.parse(plugin_xml)
340         plugin = self.loader.plugin
341         plugin.path = os.path.dirname(plugin_xml)
342         self.plugins[plugin.name] = plugin
343
344     def load_plugins_from_dir(self, plugin_dir):
345         log.debug('Loading plugins from %s' % plugin_dir)
346         if not os.path.isdir(plugin_dir):
347             return
348         for plugin_xml in glob.glob(os.path.join(plugin_dir, '*', 'plugin.xml')):
349             try:
350                 self.load_plugin(plugin_xml)
351             except Exception, e:
352                 log.error('Could not load plugin definition %s' % plugin_xml, e)
353
354     def get_plugins(self):
355         return self.plugins.values()
356
357 # Make one default plugin manager
358 import gaphor
359 _default_plugin_manager = gaphor.resource(PluginManager)
360 del gaphor
361
362 # vim:sw=4:et
Note: See TracBrowser for help on using the browser.