]>
Commit | Line | Data |
---|---|---|
1 | """Provides a variety of introspective-type support functions for | |
2 | things like call tips and command auto completion.""" | |
3 | ||
4 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" | |
5 | __cvsid__ = "$Id$" | |
6 | __revision__ = "$Revision$"[11:-2] | |
7 | ||
8 | from __future__ import nested_scopes | |
9 | ||
10 | import cStringIO | |
11 | import inspect | |
12 | import sys | |
13 | import tokenize | |
14 | import types | |
15 | ||
16 | def getAutoCompleteList(command='', locals=None, includeMagic=1, | |
17 | includeSingle=1, includeDouble=1): | |
18 | """Return list of auto-completion options for command. | |
19 | ||
20 | The list of options will be based on the locals namespace.""" | |
21 | attributes = [] | |
22 | # Get the proper chunk of code from the command. | |
23 | root = getRoot(command, terminator='.') | |
24 | try: | |
25 | if locals is not None: | |
26 | object = eval(root, locals) | |
27 | else: | |
28 | object = eval(root) | |
29 | except: | |
30 | pass | |
31 | else: | |
32 | attributes = getAttributeNames(object, includeMagic, | |
33 | includeSingle, includeDouble) | |
34 | return attributes | |
35 | ||
36 | def getAttributeNames(object, includeMagic=1, includeSingle=1, | |
37 | includeDouble=1): | |
38 | """Return list of unique attributes, including inherited, for object.""" | |
39 | attributes = [] | |
40 | dict = {} | |
41 | if not hasattrAlwaysReturnsTrue(object): | |
42 | # Add some attributes that don't always get picked up. If | |
43 | # they don't apply, they'll get filtered out at the end. | |
44 | attributes += ['__bases__', '__class__', '__dict__', '__name__', | |
45 | 'func_closure', 'func_code', 'func_defaults', | |
46 | 'func_dict', 'func_doc', 'func_globals', 'func_name'] | |
47 | if includeMagic: | |
48 | try: attributes += object._getAttributeNames() | |
49 | except: pass | |
50 | # Get all attribute names. | |
51 | attrdict = getAllAttributeNames(object) | |
52 | for attrlist in attrdict.values(): | |
53 | attributes += attrlist | |
54 | # Remove duplicates from the attribute list. | |
55 | for item in attributes: | |
56 | dict[item] = None | |
57 | attributes = dict.keys() | |
58 | # new-style swig wrappings can result in non-string attributes | |
59 | # e.g. ITK http://www.itk.org/ | |
60 | attributes = [attribute for attribute in attributes \ | |
61 | if type(attribute) == str] | |
62 | attributes.sort(lambda x, y: cmp(x.upper(), y.upper())) | |
63 | if not includeSingle: | |
64 | attributes = filter(lambda item: item[0]!='_' \ | |
65 | or item[1]=='_', attributes) | |
66 | if not includeDouble: | |
67 | attributes = filter(lambda item: item[:2]!='__', attributes) | |
68 | # Make sure we haven't picked up any bogus attributes somehow. | |
69 | attributes = [attribute for attribute in attributes \ | |
70 | if hasattr(object, attribute)] | |
71 | return attributes | |
72 | ||
73 | def hasattrAlwaysReturnsTrue(object): | |
74 | return hasattr(object, 'bogu5_123_aTTri8ute') | |
75 | ||
76 | def getAllAttributeNames(object): | |
77 | """Return dict of all attributes, including inherited, for an object. | |
78 | ||
79 | Recursively walk through a class and all base classes. | |
80 | """ | |
81 | attrdict = {} # (object, technique, count): [list of attributes] | |
82 | # !!! | |
83 | # Do Not use hasattr() as a test anywhere in this function, | |
84 | # because it is unreliable with remote objects: xmlrpc, soap, etc. | |
85 | # They always return true for hasattr(). | |
86 | # !!! | |
87 | try: | |
88 | # Yes, this can fail if object is an instance of a class with | |
89 | # __str__ (or __repr__) having a bug or raising an | |
90 | # exception. :-( | |
91 | key = str(object) | |
92 | except: | |
93 | key = 'anonymous' | |
94 | # Wake up sleepy objects - a hack for ZODB objects in "ghost" state. | |
95 | wakeupcall = dir(object) | |
96 | del wakeupcall | |
97 | # Get attributes available through the normal convention. | |
98 | attributes = dir(object) | |
99 | attrdict[(key, 'dir', len(attributes))] = attributes | |
100 | # Get attributes from the object's dictionary, if it has one. | |
101 | try: | |
102 | attributes = object.__dict__.keys() | |
103 | attributes.sort() | |
104 | except: # Must catch all because object might have __getattr__. | |
105 | pass | |
106 | else: | |
107 | attrdict[(key, '__dict__', len(attributes))] = attributes | |
108 | # For a class instance, get the attributes for the class. | |
109 | try: | |
110 | klass = object.__class__ | |
111 | except: # Must catch all because object might have __getattr__. | |
112 | pass | |
113 | else: | |
114 | if klass is object: | |
115 | # Break a circular reference. This happens with extension | |
116 | # classes. | |
117 | pass | |
118 | else: | |
119 | attrdict.update(getAllAttributeNames(klass)) | |
120 | # Also get attributes from any and all parent classes. | |
121 | try: | |
122 | bases = object.__bases__ | |
123 | except: # Must catch all because object might have __getattr__. | |
124 | pass | |
125 | else: | |
126 | if isinstance(bases, types.TupleType): | |
127 | for base in bases: | |
128 | if type(base) is types.TypeType: | |
129 | # Break a circular reference. Happens in Python 2.2. | |
130 | pass | |
131 | else: | |
132 | attrdict.update(getAllAttributeNames(base)) | |
133 | return attrdict | |
134 | ||
135 | def getCallTip(command='', locals=None): | |
136 | """For a command, return a tuple of object name, argspec, tip text. | |
137 | ||
138 | The call tip information will be based on the locals namespace.""" | |
139 | calltip = ('', '', '') # object name, argspec, tip text. | |
140 | # Get the proper chunk of code from the command. | |
141 | root = getRoot(command, terminator='(') | |
142 | try: | |
143 | if locals is not None: | |
144 | object = eval(root, locals) | |
145 | else: | |
146 | object = eval(root) | |
147 | except: | |
148 | return calltip | |
149 | name = '' | |
150 | object, dropSelf = getBaseObject(object) | |
151 | try: | |
152 | name = object.__name__ | |
153 | except AttributeError: | |
154 | pass | |
155 | tip1 = '' | |
156 | argspec = '' | |
157 | if inspect.isbuiltin(object): | |
158 | # Builtin functions don't have an argspec that we can get. | |
159 | pass | |
160 | elif inspect.isfunction(object): | |
161 | # tip1 is a string like: "getCallTip(command='', locals=None)" | |
162 | argspec = apply(inspect.formatargspec, inspect.getargspec(object)) | |
163 | if dropSelf: | |
164 | # The first parameter to a method is a reference to an | |
165 | # instance, usually coded as "self", and is usually passed | |
166 | # automatically by Python; therefore we want to drop it. | |
167 | temp = argspec.split(',') | |
168 | if len(temp) == 1: # No other arguments. | |
169 | argspec = '()' | |
170 | elif temp[0][:2] == '(*': # first param is like *args, not self | |
171 | pass | |
172 | else: # Drop the first argument. | |
173 | argspec = '(' + ','.join(temp[1:]).lstrip() | |
174 | tip1 = name + argspec | |
175 | doc = '' | |
176 | if callable(object): | |
177 | try: | |
178 | doc = inspect.getdoc(object) | |
179 | except: | |
180 | pass | |
181 | if doc: | |
182 | # tip2 is the first separated line of the docstring, like: | |
183 | # "Return call tip text for a command." | |
184 | # tip3 is the rest of the docstring, like: | |
185 | # "The call tip information will be based on ... <snip> | |
186 | firstline = doc.split('\n')[0].lstrip() | |
187 | if tip1 == firstline or firstline[:len(name)+1] == name+'(': | |
188 | tip1 = '' | |
189 | else: | |
190 | tip1 += '\n\n' | |
191 | docpieces = doc.split('\n\n') | |
192 | tip2 = docpieces[0] | |
193 | tip3 = '\n\n'.join(docpieces[1:]) | |
194 | tip = '%s%s\n\n%s' % (tip1, tip2, tip3) | |
195 | else: | |
196 | tip = tip1 | |
197 | calltip = (name, argspec[1:-1], tip.strip()) | |
198 | return calltip | |
199 | ||
200 | def getRoot(command, terminator=None): | |
201 | """Return the rightmost root portion of an arbitrary Python command. | |
202 | ||
203 | Return only the root portion that can be eval()'d without side | |
204 | effects. The command would normally terminate with a '(' or | |
205 | '.'. The terminator and anything after the terminator will be | |
206 | dropped.""" | |
207 | command = command.split('\n')[-1] | |
208 | if command.startswith(sys.ps2): | |
209 | command = command[len(sys.ps2):] | |
210 | command = command.lstrip() | |
211 | command = rtrimTerminus(command, terminator) | |
212 | tokens = getTokens(command) | |
213 | if not tokens: | |
214 | return '' | |
215 | if tokens[-1][0] is tokenize.ENDMARKER: | |
216 | # Remove the end marker. | |
217 | del tokens[-1] | |
218 | if not tokens: | |
219 | return '' | |
220 | if terminator == '.' and \ | |
221 | (tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP): | |
222 | # Trap decimals in numbers, versus the dot operator. | |
223 | return '' | |
224 | else: | |
225 | # Strip off the terminator. | |
226 | if terminator and command.endswith(terminator): | |
227 | size = 0 - len(terminator) | |
228 | command = command[:size] | |
229 | command = command.rstrip() | |
230 | tokens = getTokens(command) | |
231 | tokens.reverse() | |
232 | line = '' | |
233 | start = None | |
234 | prefix = '' | |
235 | laststring = '.' | |
236 | emptyTypes = ('[]', '()', '{}') | |
237 | for token in tokens: | |
238 | tokentype = token[0] | |
239 | tokenstring = token[1] | |
240 | line = token[4] | |
241 | if tokentype is tokenize.ENDMARKER: | |
242 | continue | |
243 | if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \ | |
244 | and laststring != '.': | |
245 | # We've reached something that's not part of the root. | |
246 | if prefix and line[token[3][1]] != ' ': | |
247 | # If it doesn't have a space after it, remove the prefix. | |
248 | prefix = '' | |
249 | break | |
250 | if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \ | |
251 | or (tokentype is tokenize.OP and tokenstring == '.'): | |
252 | if prefix: | |
253 | # The prefix isn't valid because it comes after a dot. | |
254 | prefix = '' | |
255 | break | |
256 | else: | |
257 | # start represents the last known good point in the line. | |
258 | start = token[2][1] | |
259 | elif len(tokenstring) == 1 and tokenstring in ('[({])}'): | |
260 | # Remember, we're working backwords. | |
261 | # So prefix += tokenstring would be wrong. | |
262 | if prefix in emptyTypes and tokenstring in ('[({'): | |
263 | # We've already got an empty type identified so now we | |
264 | # are in a nested situation and we can break out with | |
265 | # what we've got. | |
266 | break | |
267 | else: | |
268 | prefix = tokenstring + prefix | |
269 | else: | |
270 | # We've reached something that's not part of the root. | |
271 | break | |
272 | laststring = tokenstring | |
273 | if start is None: | |
274 | start = len(line) | |
275 | root = line[start:] | |
276 | if prefix in emptyTypes: | |
277 | # Empty types are safe to be eval()'d and introspected. | |
278 | root = prefix + root | |
279 | return root | |
280 | ||
281 | def getTokens(command): | |
282 | """Return list of token tuples for command.""" | |
283 | command = str(command) # In case the command is unicode, which fails. | |
284 | f = cStringIO.StringIO(command) | |
285 | # tokens is a list of token tuples, each looking like: | |
286 | # (type, string, (srow, scol), (erow, ecol), line) | |
287 | tokens = [] | |
288 | # Can't use list comprehension: | |
289 | # tokens = [token for token in tokenize.generate_tokens(f.readline)] | |
290 | # because of need to append as much as possible before TokenError. | |
291 | try: | |
292 | ## This code wasn't backward compatible with Python 2.1.3. | |
293 | ## | |
294 | ## for token in tokenize.generate_tokens(f.readline): | |
295 | ## tokens.append(token) | |
296 | ||
297 | # This works with Python 2.1.3 (with nested_scopes). | |
298 | def eater(*args): | |
299 | tokens.append(args) | |
300 | tokenize.tokenize_loop(f.readline, eater) | |
301 | except tokenize.TokenError: | |
302 | # This is due to a premature EOF, which we expect since we are | |
303 | # feeding in fragments of Python code. | |
304 | pass | |
305 | return tokens | |
306 | ||
307 | def rtrimTerminus(command, terminator=None): | |
308 | """Return command minus anything that follows the final terminator.""" | |
309 | if terminator: | |
310 | pieces = command.split(terminator) | |
311 | if len(pieces) > 1: | |
312 | command = terminator.join(pieces[:-1]) + terminator | |
313 | return command | |
314 | ||
315 | def getBaseObject(object): | |
316 | """Return base object and dropSelf indicator for an object.""" | |
317 | if inspect.isbuiltin(object): | |
318 | # Builtin functions don't have an argspec that we can get. | |
319 | dropSelf = 0 | |
320 | elif inspect.ismethod(object): | |
321 | # Get the function from the object otherwise | |
322 | # inspect.getargspec() complains that the object isn't a | |
323 | # Python function. | |
324 | try: | |
325 | if object.im_self is None: | |
326 | # This is an unbound method so we do not drop self | |
327 | # from the argspec, since an instance must be passed | |
328 | # as the first arg. | |
329 | dropSelf = 0 | |
330 | else: | |
331 | dropSelf = 1 | |
332 | object = object.im_func | |
333 | except AttributeError: | |
334 | dropSelf = 0 | |
335 | elif inspect.isclass(object): | |
336 | # Get the __init__ method function for the class. | |
337 | constructor = getConstructor(object) | |
338 | if constructor is not None: | |
339 | object = constructor | |
340 | dropSelf = 1 | |
341 | else: | |
342 | dropSelf = 0 | |
343 | elif callable(object): | |
344 | # Get the __call__ method instead. | |
345 | try: | |
346 | object = object.__call__.im_func | |
347 | dropSelf = 1 | |
348 | except AttributeError: | |
349 | dropSelf = 0 | |
350 | else: | |
351 | dropSelf = 0 | |
352 | return object, dropSelf | |
353 | ||
354 | def getConstructor(object): | |
355 | """Return constructor for class object, or None if there isn't one.""" | |
356 | try: | |
357 | return object.__init__.im_func | |
358 | except AttributeError: | |
359 | for base in object.__bases__: | |
360 | constructor = getConstructor(base) | |
361 | if constructor is not None: | |
362 | return constructor | |
363 | return None |