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