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

Revision 465, 10.5 kB (checked in by arjanmol, 4 years ago)

*** empty log message ***

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 # vim:sw=4
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 from gaphor.parser import ParserException
32 from gaphor.misc.action import register_action_for_slot
33 from gaphor.misc.odict import odict
34
35 XMLNS='http://gaphor.sourceforge.net/gaphor/plugin'
36
37 # Directories to look for plugins. These sirectories are added to the
38 # search path. User provided plugins overrule system plugins.
39 DEFAULT_PLUGIN_DIRS = [os.path.join(resource('DataDir'), 'plugins'),
40                        os.path.join(resource('UserDataDir'), 'plugins')]
41
42 #log.debug('sys.path=' + str(sys.path))
43 #log.debug('DEFAULT_PLUGIN_DIRS=' + str(DEFAULT_PLUGIN_DIRS))
44
45 class Plugin(object):
46     """A plugin represents one plugin loaded from the file system.
47     """
48
49     def __init__(self):
50         self.required_modules = []
51         self.required_plugins = []
52         self.required_actions = []
53         self.provided_actions = []
54         self.description = ''
55         self.initialized = False
56         self.path = ''
57         self.module = None
58         self.status = ''
59
60     def requirements_met(self, manager):
61         """Check if all <require>-ments are met to load the plugin.
62         """
63         if self.initialized:
64             return False
65
66         for mod in self.required_modules:
67             try:
68                 # TODO: How to find out if a module exists without loading it?
69                 __import__(mod, globals(), locals(), [])
70             except ImportError:
71                 self.initialized = True
72                 self.status = 'Could not initialize plugin: module %s could not be imported' % mod
73                 log.debug(self.status)
74                 return False
75
76         pluginstatus = {}
77         for p in manager.plugins.itervalues():
78             pluginstatus[p.name] = bool(p.initialized)
79         for p in self.required_plugins:
80             if not pluginstatus.get(p):
81                 self.status = 'Plugin %s is a prerequisite' % p
82                 log.debug(self.status + ' for %s' % self.name)
83                 return False
84
85         return True
86
87     def import_plugin(self):
88         """Do the actual import of the plugin module.
89         """
90         #mod = __import__(self.path.split(os.sep)[-1], globals(), locals(), [])
91         name = os.path.split(self.path)[1]
92         f, n, d = imp.find_module(name, DEFAULT_PLUGIN_DIRS)
93         mod = imp.load_module(name, f, n, d)
94         self.module = mod
95         self.initialized = True
96         if mod:
97             self.status = 'Imported'
98
99         for action in self.provided_actions:
100             try:
101                 action.import_action(self)
102             except Exception, e:
103                 self.status += '\nFailed to import action %s (%s)' % (action.id or action.class_, e)
104                 log.error('Failed to import action %s' % (action.id or action.class_), e)
105
106
107 class PluginAction(object):
108     """Each action within a Plugin is represented by a PluginAction.
109     """
110
111     def __init__(self, class_, slot):
112         self.class_ = class_
113         self.slot = slot
114         self.id = None
115         self.stock_id = None
116         self.icon_file = None
117         self.tooltip = None
118         self.label = None
119         self.accel = None
120         self.depends = []
121
122     def import_action(self, plugin):
123         """Import and register one action in the plugin.
124         """
125         # Create an icon for the plugin
126         if self.icon_file:
127             from gaphor.ui.stock import add_stock_icon
128             self.stock_id = 'gaphor-plugin-' + self.id
129             add_stock_icon(self.stock_id, plugin.path, [self.icon_file])
130
131         # Fetch the action class
132         action_class = getattr(plugin.module, self.class_)
133
134         # Copy attributes from the plugin to the class
135         for attr in ('id', 'label', 'stock_id', 'tooltip', 'accel'):
136             val = getattr(self, attr, None)
137             if val:
138                 setattr(action_class, attr, val)
139
140         register_action_for_slot(action_class, self.slot, *self.depends)
141         log.debug('Providing action %s for slot %s' % (action_class, self.slot))
142
143 class PluginLoader(handler.ContentHandler):
144     """Load a Plugin definition file (plugin.xml).
145     """
146     TOPLEVEL = 0
147     REQUIRE = 1
148     PROVIDE = 2
149     ACTION = 3
150     DESCRIPTION = 4
151
152     def __init__(self):
153         handler.ContentHandler.__init__(self)
154
155     def endDTD(self):
156         pass
157
158     def startDocument(self):
159         """Start of document: all our attributes are initialized.
160         """
161         self.plugin = Plugin()
162         self.mode = self.TOPLEVEL
163
164     def endDocument(self):
165         pass
166
167     def startElement(self, name, attrs):
168
169         mode = self.mode
170         if mode == self.TOPLEVEL:
171             if name == 'plugin':
172                 self.plugin.name = attrs['name']
173                 self.plugin.version = attrs['version']
174                 self.plugin.author = attrs['author']
175             elif name == 'description':
176                 self.mode = self.DESCRIPTION
177             if name == 'require':
178                 self.mode = self.REQUIRE
179             elif name == 'provide':
180                 self.mode = self.PROVIDE
181
182         elif mode == self.REQUIRE:
183             if name == 'module':
184                 self.plugin.required_modules.append(attrs['name'])
185             elif name == 'action':
186                 self.plugin.required_actions.append(attrs['name'])
187             elif name == 'plugin':
188                 self.plugin.required_plugins.append(attrs['name'])
189
190         elif mode == self.PROVIDE:
191             if name == 'action':
192                 action = PluginAction(attrs['class'].encode(),
193                                           attrs['slot'].encode())
194                 action.id = attrs.get('id', '').encode()
195                 action.stock_id = attrs.get('stock-id', '').encode()
196                 action.icon_file = attrs.get('icon-file', '').encode()
197                 action.tooltip = attrs.get('tooltip', '').encode()
198                 action.label = attrs.get('label', '').encode()
199                 action.accel = attrs.get('accel', '').encode()
200                 self.plugin.provided_actions.append(action)
201                 self.mode = self.ACTION
202
203         elif mode == self.ACTION:
204             if name == 'depends':
205                 self.plugin.provided_actions[-1].depends.append(attrs['action'].encode())
206
207         else:
208             raise ParserException, 'Invalid XML: tag <%s> not known' % name
209
210     def endElement(self, name):
211         if self.mode in (self.REQUIRE, self.PROVIDE, self.DESCRIPTION) and \
212            name in ('description', 'require', 'provide'):
213             self.mode = self.TOPLEVEL
214         elif self.mode == self.ACTION and name == 'action':
215             self.mode = self.PROVIDE
216         elif name in ('plugin', 'module', 'depends'):
217             pass
218         else:
219             raise ParserException, 'Invalid XML: tag <%s> not known' % name
220
221     def startElementNS(self, name, qname, attrs):
222         if not name[0] or name[0] == XMLNS:
223             a = { }
224             for key, val in attrs.items():
225                 a[key[1]] = val
226             self.startElement(name[1], a)
227
228     def endElementNS(self, name, qname):
229         if not name[0] or name[0] == XMLNS:
230             self.endElement(name[1])
231
232     def characters(self, content):
233         """Read characters."""
234         if self.mode == self.DESCRIPTION:
235             self.plugin.description += content
236
237
238 class PluginManager(object):
239     """The PluginManager is the main point where plugins are managed.
240     """
241
242     def __init__(self):
243         self.plugins = odict()
244         self.bootstrapped = False
245
246         # Create an XML parser
247         self.parser = make_parser()
248         self.loader = PluginLoader()
249         self.parser.setFeature(handler.feature_namespaces, 1)
250         self.parser.setContentHandler(self.loader)
251
252     def bootstrap(self):
253         """Do the normal plugin loading.
254         """
255         if self.bootstrapped:
256             return
257
258         # Load the plugins in reverse order, so the user plugins will
259         # overwrite the default plugins. (they are imported in sys.path as
260         # [user plugins, default plugins]).
261         for plugin_dir in DEFAULT_PLUGIN_DIRS:
262             self.load_plugins_from_dir(plugin_dir)
263
264         import_done = True
265         while import_done:
266             import_done = False
267             for plugin in self.plugins.itervalues():
268                 if plugin.requirements_met(self):
269                     try:
270                         plugin.import_plugin()
271                     except Exception, e:
272                         plugin.status = 'Failed to load plugin %s: %s' % (plugin.name, e)
273                         log.error('Failed to load plugin %s' % plugin.name, e)
274                     else:
275                         import_done = True
276
277         self.bootstrapped = True
278
279     def load_plugin(self, plugin_xml):
280         """Load a plugin definition and store the plugin in the manager.
281         """
282         assert not self.plugins.has_key(os.path.dirname(plugin_xml))
283         self.parser.parse(plugin_xml)
284         plugin = self.loader.plugin
285         plugin.path = os.path.dirname(plugin_xml)
286         self.plugins[plugin.name] = plugin
287
288     def load_plugins_from_dir(self, plugin_dir):
289         log.debug('Loading plugins from %s' % plugin_dir)
290         if not os.path.isdir(plugin_dir):
291             return
292         for plugin_xml in glob.glob(os.path.join(plugin_dir, '*', 'plugin.xml')):
293             try:
294                 self.load_plugin(plugin_xml)
295             except Exception, e:
296                 log.error('Could not load plugin definition %s' % plugin_xml, e)
297
298     def get_plugins(self):
299         return self.plugins.values()
300
301 # Make one default plugin manager
302 import gaphor
303 _default_plugin_manager = gaphor.resource(PluginManager)
304 del gaphor
305
Note: See TracBrowser for help on using the browser.