OLD | NEW |
| (Empty) |
1 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
2 # See LICENSE for details. | |
3 | |
4 | |
5 from __future__ import nested_scopes | |
6 | |
7 # System Imports | |
8 import sys | |
9 import os | |
10 import errno | |
11 import types | |
12 import warnings | |
13 | |
14 # Twisted imports | |
15 from twisted.python import util | |
16 | |
17 # Sibling Imports | |
18 from reflect import namedModule | |
19 | |
20 try: | |
21 from os.path import realpath as cacheTransform | |
22 except ImportError: | |
23 from os.path import abspath as cacheTransform | |
24 | |
25 class PlugIn: | |
26 """I am a Python module registered in a plugins.tml file. | |
27 """ | |
28 def __init__(self, name, module, **kw): | |
29 self.name = name | |
30 self.module = module | |
31 for key, value in kw.items(): | |
32 setattr(self, key, value) | |
33 | |
34 def isLoaded(self): | |
35 """Check to see if the module for this plugin has been imported yet. | |
36 | |
37 @rtype: C{int} | |
38 @return: A true value if the module for this plugin has been loaded, | |
39 false otherwise. | |
40 """ | |
41 return sys.modules.has_key(self.module) | |
42 | |
43 def load(self): | |
44 """Load the module for this plugin. | |
45 | |
46 @rtype: C{ModuleType} | |
47 @return: The module object that is loaded. | |
48 """ | |
49 return namedModule(self.module) | |
50 | |
51 def __repr__(self): | |
52 if self.isLoaded(): | |
53 loaded = ' loaded' | |
54 else: | |
55 loaded = '' | |
56 return "<Plugin %s %s%s>" % (repr(self.name), self.module, loaded) | |
57 | |
58 class DropIn: | |
59 """I am a Python package containing plugins.tml. | |
60 """ | |
61 def __init__(self, name): | |
62 self.name = name | |
63 self.plugins = [] | |
64 | |
65 def register(self, name, module, **kw): | |
66 """Register a new plug-in. | |
67 """ | |
68 warnings.warn("The twisted.python.plugin system is deprecated. " | |
69 "See twisted.plugin for the revised edition.", | |
70 DeprecationWarning, 2) | |
71 self.plugins.append(PlugIn(name, module, **kw)) | |
72 | |
73 def __repr__(self): | |
74 return "<Package %s %s>" % (self.name, self.plugins) | |
75 | |
76 | |
77 def _prepCallbacks(debug, progress): | |
78 if debug: | |
79 try: | |
80 debug('Looking for plugin.tml files') | |
81 except: | |
82 debug = lambda x: sys.stdout.write(x + '\n') | |
83 debug('Looking for plugin.tml files') | |
84 else: | |
85 debug = lambda x: None | |
86 if progress: | |
87 try: | |
88 progress(0.0) | |
89 except: | |
90 pb = util.makeStatusBar(76) | |
91 progress = lambda x, pb=pb: sys.stdout.write(pb(x) + '\r') | |
92 progress(0.0) | |
93 else: | |
94 progress = lambda x: None | |
95 return debug, progress | |
96 | |
97 def getPluginFileList(debugInspection=None, showProgress=None): | |
98 """Find plugin.tml files in subdirectories of paths in C{sys.path} | |
99 | |
100 @type debugInspection: C{None} or a callable taking one argument | |
101 @param debugInspection: If not None, this is invoked with strings containing | |
102 debug information about the loading process. If it is any other true value, | |
103 this debug information is written to stdout (This behavior is deprecated). | |
104 | |
105 @type showProgress: C{None} or a callable taking one argument. | |
106 @param showProgress: If not None, this is invoked with floating point | |
107 values between 0 and 1 describing the progress of the loading process. | |
108 If it is any other true value, this progress information is written to | |
109 stdout. (This behavior is deprecated). | |
110 | |
111 @rtype: C{list} of C{str} | |
112 @return: A list of the plugin.tml files found. | |
113 """ | |
114 if isinstance(debugInspection, types.IntType): | |
115 warnings.warn( | |
116 "int parameter for debugInspection is deprecated, pass None or " | |
117 "a function that takes a single argument instead.", | |
118 DeprecationWarning, 2 | |
119 ) | |
120 if isinstance(showProgress, types.IntType): | |
121 warnings.warn( | |
122 "int parameter for showProgress is deprecated, pass None or " | |
123 "a function that takes a single argument instead.", | |
124 DeprecationWarning, 2 | |
125 ) | |
126 debugInspection, showProgress = _prepCallbacks(debugInspection, showProgress
) | |
127 exists = os.path.exists | |
128 join = os.sep.join | |
129 result = [] | |
130 loaded = {} | |
131 seenNames = {} | |
132 | |
133 # XXX Some people claim to have found non-strings in sys.path (an empty | |
134 # list, in particular). Instead of tracking down the cause for their | |
135 # presence, they decided it was better to discard them unconditionally | |
136 # without further investigation. At some point, someone should track | |
137 # down where non-strings are coming from and do something about them. | |
138 paths = [cacheTransform(p) for p in sys.path | |
139 if isinstance(p, str) and os.path.isdir(p)] | |
140 | |
141 # special case for commonly used directories we *know* shouldn't be checked | |
142 # and really slow down mktap and such-like in real installations | |
143 for p in ("/usr/bin", "/usr/local/bin"): | |
144 try: | |
145 paths.remove(p) | |
146 except ValueError: | |
147 pass | |
148 progress = 0.0 | |
149 increments = 1.0 / len(paths) | |
150 | |
151 for (index, d) in zip(range(len(paths)), paths): | |
152 showProgress(progress) | |
153 if loaded.has_key(d): | |
154 debugInspection('Already saw ' + d) | |
155 continue | |
156 else: | |
157 debugInspection('Recursing through ' + d) | |
158 try: | |
159 subDirs = os.listdir(d) | |
160 except OSError, (err, s): | |
161 # Permission denied, carry on | |
162 if err == errno.EACCES: | |
163 debugInspection('Permission denied on ' + d) | |
164 else: | |
165 raise | |
166 else: | |
167 # filter out files we obviously don't need to check - ones with '.'
in them | |
168 subDirs = [s for s in subDirs if "." not in s] | |
169 if not subDirs: | |
170 continue | |
171 incr = increments * (1.0 / len(subDirs)) | |
172 for plugindir in subDirs: | |
173 if seenNames.has_key(plugindir): | |
174 debugInspection('Seen %s already' % plugindir) | |
175 continue | |
176 tmlname = join((d, plugindir, "plugins.tml")) | |
177 if isAModule(join((d,plugindir))): | |
178 seenNames[plugindir] = 1 | |
179 if exists(tmlname): | |
180 result.append(tmlname) | |
181 debugInspection('Found ' + tmlname) | |
182 else: | |
183 debugInspection('Failed ' + tmlname) | |
184 else: | |
185 debugInspection('Not a module ' + tmlname) | |
186 progress = progress + incr | |
187 showProgress(progress) | |
188 | |
189 showProgress(1.0) | |
190 return result | |
191 | |
192 def loadPlugins(plugInType, fileList, debugInspection=None, showProgress=None): | |
193 """Traverse the given list of files and attempt to load plugins from them. | |
194 | |
195 @type plugInType: C{str} | |
196 @param plugInType: The type of plugin to search for. This is tested | |
197 against the C{type} argument to the C{register} function in the | |
198 plugin.tml files. | |
199 | |
200 @type fileList: C{list} of C{str} | |
201 @param fileList: A list of the files to attempt to load plugin | |
202 information from. One name is put in their scope, the C{register} | |
203 function. | |
204 | |
205 @type debugInspection: C{None} or a callable taking one argument | |
206 @param debugInspection: If not None, this is invoked with strings containing | |
207 debug information about the loading process. If it is any other true value, | |
208 this debug information is written to stdout (This behavior is deprecated). | |
209 | |
210 @type showProgress: C{None} or a callable taking one argument. | |
211 @param showProgress: If not None, this is invoked with floating point | |
212 values between 0 and 1 describing the progress of the loading process. | |
213 If it is any other true value, this progress information is written to | |
214 stdout. (This behavior is deprecated). | |
215 | |
216 @rtype: C{list} | |
217 @return: A list of the C{PlugIn} objects found. | |
218 """ | |
219 if isinstance(debugInspection, types.IntType): | |
220 warnings.warn( | |
221 "int parameter for debugInspection is deprecated, pass None or " | |
222 "a function that takes a single argument instead.", | |
223 DeprecationWarning, 4 | |
224 ) | |
225 if isinstance(showProgress, types.IntType): | |
226 warnings.warn( | |
227 "int parameter for showProgress is deprecated, pass None or " | |
228 "a function that takes a single argument instead.", | |
229 DeprecationWarning, 4 | |
230 ) | |
231 result = [] | |
232 debugInspection, showProgress = _prepCallbacks(debugInspection, showProgress
) | |
233 | |
234 if not fileList: | |
235 raise ValueError("No plugins passed to loadPlugins") | |
236 | |
237 increments = 1.0 / len(fileList) | |
238 progress = 0.0 | |
239 | |
240 for (index, tmlFile) in zip(range(len(fileList)), fileList): | |
241 showProgress(progress) | |
242 debugInspection("Loading from " + tmlFile) | |
243 pname = os.path.split(os.path.abspath(tmlFile))[-2] | |
244 dropin = DropIn(pname) | |
245 ns = {'register': dropin.register, '__file__': tmlFile} | |
246 try: | |
247 execfile(tmlFile, ns) | |
248 except (IOError, OSError), e: | |
249 # guess we don't have permissions for that | |
250 debugInspection("Error loading: %s" % e) | |
251 continue | |
252 | |
253 ldp = len(dropin.plugins) or 1.0 | |
254 incr = increments * (1.0 / ldp) | |
255 for plugin in dropin.plugins: | |
256 if plugInType == plugin.type: | |
257 result.append(plugin) | |
258 debugInspection("Found %r" % (plugin,)) | |
259 else: | |
260 debugInspection("Disqualified %r" % (plugin,)) | |
261 progress = progress + incr | |
262 showProgress(progress) | |
263 debugInspection("Finished loading from %s!" % tmlFile) | |
264 | |
265 showProgress(1.0) | |
266 debugInspection("Returning %r" % (result,)) | |
267 return result | |
268 | |
269 def getPlugIns(plugInType, debugInspection=None, showProgress=None): | |
270 """Helper function to get all the plugins of a particular type. | |
271 | |
272 @type plugInType: C{str} | |
273 @param plugInType: The type of plugin to search for. This is tested | |
274 against the C{type} argument to the C{register} function in the | |
275 plugin.tml files. | |
276 | |
277 @type debugInspection: C{None} or a callable taking one argument | |
278 @param debugInspection: If not None, this is invoked with strings containing | |
279 debug information about the loading process. If it is any other true value, | |
280 this debug information is written to stdout (This behavior is deprecated). | |
281 | |
282 @type showProgress: C{None} or a callable taking one argument. | |
283 @param showProgress: If not None, this is invoked with floating point | |
284 values between 0 and 1 describing the progress of the loading process. | |
285 If it is any other true value, this progress information is written to | |
286 stdout. (This behavior is deprecated). | |
287 | |
288 @rtype: C{list} | |
289 @return: A list of C{PlugIn} objects that were found. | |
290 """ | |
291 warnings.warn("The twisted.python.plugin system is deprecated. " | |
292 "See twisted.plugin for the revised edition.", | |
293 DeprecationWarning, 2) | |
294 return _getPlugIns(plugInType, debugInspection, showProgress) | |
295 | |
296 def _getPlugIns(plugInType, debugInspection=None, showProgress=None): | |
297 if isinstance(debugInspection, types.IntType): | |
298 warnings.warn( | |
299 "int parameter for debugInspection is deprecated, pass None or " | |
300 "a function that takes a single argument instead.", | |
301 DeprecationWarning, 3 | |
302 ) | |
303 if isinstance(showProgress, types.IntType): | |
304 warnings.warn( | |
305 "int parameter for showProgress is deprecated, pass None or " | |
306 "a function that takes a single argument instead.", | |
307 DeprecationWarning, 3 | |
308 ) | |
309 debugInspection, showProgress = _prepCallbacks(debugInspection, showProgress
) | |
310 | |
311 firstHalf = secondHalf = lambda x: None | |
312 if showProgress: | |
313 firstHalf = lambda x: showProgress(x / 2.0) | |
314 secondHalf = lambda x: showProgress(x / 2.0 + 0.5) | |
315 | |
316 tmlFiles = getPluginFileList(debugInspection, firstHalf) | |
317 if not tmlFiles: | |
318 return [] | |
319 return loadPlugins(plugInType, tmlFiles, debugInspection, secondHalf) | |
320 | |
321 def isAModule(d): | |
322 """This function checks the directory for __init__ files. | |
323 """ | |
324 suffixes = ['py', 'pyc', 'pyo', 'so', 'pyd', 'dll'] | |
325 exists = os.path.exists | |
326 join = os.sep.join | |
327 for s in suffixes: # bad algorithm, but probably works | |
328 if exists(join((d,'__init__.%s' % s))): | |
329 return 1 | |
330 return 0 | |
331 | |
332 __all__ = ['PlugIn', 'DropIn', 'getPluginFileList', 'loadPlugins', 'getPlugIns'] | |
OLD | NEW |