]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/py/introspect.py
remove gtk1 code
[wxWidgets.git] / wxPython / wx / py / introspect.py
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 import cStringIO
9 import inspect
10 import sys
11 import tokenize
12 import types
13 import wx
14
15 def getAutoCompleteList(command='', locals=None, includeMagic=1,
16 includeSingle=1, includeDouble=1):
17 """Return list of auto-completion options for command.
18
19 The list of options will be based on the locals namespace."""
20 attributes = []
21 # Get the proper chunk of code from the command.
22 root = getRoot(command, terminator='.')
23 try:
24 if locals is not None:
25 object = eval(root, locals)
26 else:
27 object = eval(root)
28 except:
29 pass
30 else:
31 attributes = getAttributeNames(object, includeMagic,
32 includeSingle, includeDouble)
33 return attributes
34
35 def getAttributeNames(object, includeMagic=1, includeSingle=1,
36 includeDouble=1):
37 """Return list of unique attributes, including inherited, for object."""
38 attributes = []
39 dict = {}
40 if not hasattrAlwaysReturnsTrue(object):
41 # Add some attributes that don't always get picked up.
42 special_attrs = ['__bases__', '__class__', '__dict__', '__name__',
43 'func_closure', 'func_code', 'func_defaults',
44 'func_dict', 'func_doc', 'func_globals', 'func_name']
45 attributes += [attr for attr in special_attrs \
46 if hasattr(object, attr)]
47 if includeMagic:
48 try: attributes += object._getAttributeNames()
49 except: pass
50 # Get all attribute names.
51 str_type = str(type(object))
52 if str_type == "<type 'array'>":
53 attributes += dir(object)
54 else:
55 attrdict = getAllAttributeNames(object)
56 # Store the object's dir.
57 object_dir = dir(object)
58 for (obj_type_name, technique, count), attrlist in attrdict.items():
59 # This complexity is necessary to avoid accessing all the
60 # attributes of the object. This is very handy for objects
61 # whose attributes are lazily evaluated.
62 if type(object).__name__ == obj_type_name and technique == 'dir':
63 attributes += attrlist
64 else:
65 attributes += [attr for attr in attrlist \
66 if attr not in object_dir and hasattr(object, attr)]
67
68 # Remove duplicates from the attribute list.
69 for item in attributes:
70 dict[item] = None
71 attributes = dict.keys()
72 # new-style swig wrappings can result in non-string attributes
73 # e.g. ITK http://www.itk.org/
74 attributes = [attribute for attribute in attributes \
75 if type(attribute) == str]
76 attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
77 if not includeSingle:
78 attributes = filter(lambda item: item[0]!='_' \
79 or item[1:2]=='_', attributes)
80 if not includeDouble:
81 attributes = filter(lambda item: item[:2]!='__', attributes)
82 return attributes
83
84 def hasattrAlwaysReturnsTrue(object):
85 return hasattr(object, 'bogu5_123_aTTri8ute')
86
87 def getAllAttributeNames(object):
88 """Return dict of all attributes, including inherited, for an object.
89
90 Recursively walk through a class and all base classes.
91 """
92 attrdict = {} # (object, technique, count): [list of attributes]
93 # !!!
94 # Do Not use hasattr() as a test anywhere in this function,
95 # because it is unreliable with remote objects: xmlrpc, soap, etc.
96 # They always return true for hasattr().
97 # !!!
98 try:
99 # This could(?) fail if the type is poorly defined without
100 # even a name.
101 key = type(object).__name__
102 except:
103 key = 'anonymous'
104 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
105 wakeupcall = dir(object)
106 del wakeupcall
107 # Get attributes available through the normal convention.
108 attributes = dir(object)
109 attrdict[(key, 'dir', len(attributes))] = attributes
110 # Get attributes from the object's dictionary, if it has one.
111 try:
112 attributes = object.__dict__.keys()
113 attributes.sort()
114 except: # Must catch all because object might have __getattr__.
115 pass
116 else:
117 attrdict[(key, '__dict__', len(attributes))] = attributes
118 # For a class instance, get the attributes for the class.
119 try:
120 klass = object.__class__
121 except: # Must catch all because object might have __getattr__.
122 pass
123 else:
124 if klass is object:
125 # Break a circular reference. This happens with extension
126 # classes.
127 pass
128 else:
129 attrdict.update(getAllAttributeNames(klass))
130 # Also get attributes from any and all parent classes.
131 try:
132 bases = object.__bases__
133 except: # Must catch all because object might have __getattr__.
134 pass
135 else:
136 if isinstance(bases, types.TupleType):
137 for base in bases:
138 if type(base) is types.TypeType:
139 # Break a circular reference. Happens in Python 2.2.
140 pass
141 else:
142 attrdict.update(getAllAttributeNames(base))
143 return attrdict
144
145 def getCallTip(command='', locals=None):
146 """For a command, return a tuple of object name, argspec, tip text.
147
148 The call tip information will be based on the locals namespace."""
149 calltip = ('', '', '') # object name, argspec, tip text.
150 # Get the proper chunk of code from the command.
151 root = getRoot(command, terminator='(')
152 try:
153 if locals is not None:
154 object = eval(root, locals)
155 else:
156 object = eval(root)
157 except:
158 return calltip
159 name = ''
160 object, dropSelf = getBaseObject(object)
161 try:
162 name = object.__name__
163 except AttributeError:
164 pass
165 tip1 = ''
166 argspec = ''
167 if inspect.isbuiltin(object):
168 # Builtin functions don't have an argspec that we can get.
169 pass
170 elif inspect.isfunction(object):
171 # tip1 is a string like: "getCallTip(command='', locals=None)"
172 argspec = apply(inspect.formatargspec, inspect.getargspec(object))
173 if dropSelf:
174 # The first parameter to a method is a reference to an
175 # instance, usually coded as "self", and is usually passed
176 # automatically by Python; therefore we want to drop it.
177 temp = argspec.split(',')
178 if len(temp) == 1: # No other arguments.
179 argspec = '()'
180 elif temp[0][:2] == '(*': # first param is like *args, not self
181 pass
182 else: # Drop the first argument.
183 argspec = '(' + ','.join(temp[1:]).lstrip()
184 tip1 = name + argspec
185 doc = ''
186 if callable(object):
187 try:
188 doc = inspect.getdoc(object)
189 except:
190 pass
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 or firstline[:len(name)+1] == name+'(':
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 def getTokens(command):
292 """Return list of token tuples for command."""
293
294 # In case the command is unicode try encoding it
295 if type(command) == unicode:
296 try:
297 command = command.encode(wx.GetDefaultPyEncoding())
298 except UnicodeEncodeError:
299 pass # otherwise leave it alone
300
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
314 # This works with Python 2.1.3 (with nested_scopes).
315 def eater(*args):
316 tokens.append(args)
317 tokenize.tokenize_loop(f.readline, eater)
318 except tokenize.TokenError:
319 # This is due to a premature EOF, which we expect since we are
320 # feeding in fragments of Python code.
321 pass
322 return tokens
323
324 def rtrimTerminus(command, terminator=None):
325 """Return command minus anything that follows the final terminator."""
326 if terminator:
327 pieces = command.split(terminator)
328 if len(pieces) > 1:
329 command = terminator.join(pieces[:-1]) + terminator
330 return command
331
332 def getBaseObject(object):
333 """Return base object and dropSelf indicator for an object."""
334 if inspect.isbuiltin(object):
335 # Builtin functions don't have an argspec that we can get.
336 dropSelf = 0
337 elif inspect.ismethod(object):
338 # Get the function from the object otherwise
339 # inspect.getargspec() complains that the object isn't a
340 # Python function.
341 try:
342 if object.im_self is None:
343 # This is an unbound method so we do not drop self
344 # from the argspec, since an instance must be passed
345 # as the first arg.
346 dropSelf = 0
347 else:
348 dropSelf = 1
349 object = object.im_func
350 except AttributeError:
351 dropSelf = 0
352 elif inspect.isclass(object):
353 # Get the __init__ method function for the class.
354 constructor = getConstructor(object)
355 if constructor is not None:
356 object = constructor
357 dropSelf = 1
358 else:
359 dropSelf = 0
360 elif callable(object):
361 # Get the __call__ method instead.
362 try:
363 object = object.__call__.im_func
364 dropSelf = 1
365 except AttributeError:
366 dropSelf = 0
367 else:
368 dropSelf = 0
369 return object, dropSelf
370
371 def getConstructor(object):
372 """Return constructor for class object, or None if there isn't one."""
373 try:
374 return object.__init__.im_func
375 except AttributeError:
376 for base in object.__bases__:
377 constructor = getConstructor(base)
378 if constructor is not None:
379 return constructor
380 return None