gvsig-scripting / org.gvsig.scripting.app / trunk / org.gvsig.scripting.app / org.gvsig.scripting.app.extension / src / main / resources / scripting / lib / console / introspect.py @ 423
History | View | Annotate | Download (13.8 KB)
1 |
"""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: introspect.py 5910 2006-06-20 10:03:31Z jmvivo $"
|
11 |
__revision__ = "$Revision: 5910 $"[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 |
class _EaterTokens: |
292 |
def __init__(self,tokens): |
293 |
self.tokens = tokens
|
294 |
|
295 |
def __call__(self,*args): |
296 |
self.tokens.append(args)
|
297 |
|
298 |
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 |
## 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 |
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 |