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