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