]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/introspect.py
d568dc6c6fe3ff1a901a092acac1289d65626106
[wxWidgets.git] / wxPython / wxPython / lib / PyCrust / introspect.py
1 """Provides a variety of introspective-type support functions for things
2 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 inspect
9 import string
10 import types
11
12 def getAutoCompleteList(command='', locals=None, includeMagic=1, \
13 includeSingle=1, includeDouble=1):
14 """Return list of auto-completion options for command.
15
16 The list of options will be based on the locals namespace."""
17 attributes = []
18 # Get the proper chunk of code from the command.
19 root = getRoot(command, terminator='.')
20 try:
21 if locals is not None:
22 object = eval(root, locals)
23 else:
24 object = eval(root)
25 except:
26 pass
27 else:
28 attributes = getAttributeNames(object, includeMagic, \
29 includeSingle, includeDouble)
30 return attributes
31
32 def getAttributeNames(object, includeMagic=1, includeSingle=1, includeDouble=1):
33 """Return list of unique attributes, including inherited, for an object."""
34 attributes = []
35 dict = {}
36 if not hasattrAlwaysReturnsTrue(object):
37 # Add some attributes that don't always get picked up.
38 # If they don't apply, they'll get filtered out at the end.
39 attributes += ['__bases__', '__class__', '__dict__', '__name__', \
40 'func_closure', 'func_code', 'func_defaults', \
41 'func_dict', 'func_doc', 'func_globals', 'func_name']
42 if includeMagic:
43 try: attributes += object._getAttributeNames()
44 except: pass
45 # Get all attribute names.
46 attrdict = getAllAttributeNames(object)
47 for attrlist in attrdict.values():
48 attributes += attrlist
49 # Remove duplicates from the attribute list.
50 for item in attributes:
51 dict[item] = None
52 attributes = dict.keys()
53 attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
54 if not includeSingle:
55 attributes = filter(lambda item: item[0]!='_' \
56 or item[1]=='_', attributes)
57 if not includeDouble:
58 attributes = filter(lambda item: item[:2]!='__', attributes)
59 # Make sure we haven't picked up any bogus attributes somehow.
60 attributes = [attribute for attribute in attributes \
61 if hasattr(object, attribute)]
62 return attributes
63
64 def hasattrAlwaysReturnsTrue(object):
65 return hasattr(object, 'bogu5_123_aTTri8ute')
66
67 def getAllAttributeNames(object):
68 """Return mapping of all attributes, including inherited, for an object.
69
70 Recursively walk through a class and all base classes.
71 """
72 attrdict = {} # (object, technique, count): [list of attributes]
73 # !!!
74 # !!! Do Not use hasattr() as a test anywhere in this function,
75 # !!! because it is unreliable with remote objects - xmlrpc, soap, etc.
76 # !!! They always return true for hasattr().
77 # !!!
78 try:
79 # Yes, this can fail if object is an instance of a class with
80 # __str__ (or __repr__) having a bug or raising an exception. :-(
81 key = str(object)
82 except:
83 key = 'anonymous'
84 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
85 wakeupcall = dir(object)
86 del wakeupcall
87 # Get attributes available through the normal convention.
88 attributes = dir(object)
89 attrdict[(key, 'dir', len(attributes))] = attributes
90 # Get attributes from the object's dictionary, if it has one.
91 try:
92 attributes = object.__dict__.keys()
93 attributes.sort()
94 except: # Must catch all because object might have __getattr__.
95 pass
96 else:
97 attrdict[(key, '__dict__', len(attributes))] = attributes
98 # For a class instance, get the attributes for the class.
99 try:
100 klass = object.__class__
101 except: # Must catch all because object might have __getattr__.
102 pass
103 else:
104 if klass is object:
105 # Break a circular reference. This happens with extension classes.
106 pass
107 else:
108 attrdict.update(getAllAttributeNames(klass))
109 # Also get attributes from any and all parent classes.
110 try:
111 bases = object.__bases__
112 except: # Must catch all because object might have __getattr__.
113 pass
114 else:
115 if isinstance(bases, types.TupleType):
116 for base in bases:
117 if type(base) is types.TypeType:
118 # Break a circular reference. Happens in Python 2.2.
119 pass
120 else:
121 attrdict.update(getAllAttributeNames(base))
122 return attrdict
123
124 def getCallTip(command='', locals=None):
125 """For a command, return a tuple of object name, argspec, tip text.
126
127 The call tip information will be based on the locals namespace."""
128 calltip = ('', '', '') # object name, argspec, tip text.
129 # Get the proper chunk of code from the command.
130 root = getRoot(command, terminator='(')
131 try:
132 if locals is not None:
133 object = eval(root, locals)
134 else:
135 object = eval(root)
136 except:
137 return calltip
138 name = ''
139 object, dropSelf = getBaseObject(object)
140 try:
141 name = object.__name__
142 except AttributeError:
143 pass
144 tip1 = ''
145 argspec = ''
146 if inspect.isbuiltin(object):
147 # Builtin functions don't have an argspec that we can get.
148 pass
149 elif inspect.isfunction(object):
150 # tip1 is a string like: "getCallTip(command='', locals=None)"
151 argspec = apply(inspect.formatargspec, inspect.getargspec(object))
152 if dropSelf:
153 # The first parameter to a method is a reference to an
154 # instance, usually coded as "self", and is usually passed
155 # automatically by Python and therefore we want to drop it.
156 temp = argspec.split(',')
157 if len(temp) == 1: # No other arguments.
158 argspec = '()'
159 else: # Drop the first argument.
160 argspec = '(' + ','.join(temp[1:]).lstrip()
161 tip1 = name + argspec
162 doc = ''
163 if callable(object):
164 doc = inspect.getdoc(object)
165 if doc:
166 # tip2 is the first separated line of the docstring, like:
167 # "Return call tip text for a command."
168 # tip3 is the rest of the docstring, like:
169 # "The call tip information will be based on ... <snip>
170 docpieces = doc.split('\n\n')
171 tip2 = docpieces[0]
172 tip3 = '\n\n'.join(docpieces[1:])
173 tip = '%s\n\n%s\n\n%s' % (tip1, tip2, tip3)
174 else:
175 tip = tip1
176 calltip = (name, argspec[1:-1], tip.strip())
177 return calltip
178
179 def getRoot(command, terminator=None):
180 """Return the rightmost root portion of an arbitrary Python command.
181
182 Return only the root portion that can be eval()'d without side effects.
183 The command would normally terminate with a "(" or ".". The terminator
184 and anything after the terminator will be dropped."""
185 root = ''
186 validChars = "._" + string.uppercase + string.lowercase + string.digits
187 emptyTypes = ("''", '""', '""""""', "''''''", '[]', '()', '{}')
188 validSeparators = string.whitespace + ',+-*/=%<>&|^~:([{'
189 # Drop the final terminator and anything that follows.
190 command = rtrimTerminus(command, terminator)
191 if len(command) == 0:
192 root = ''
193 elif command in emptyTypes and terminator in ('.', '', None):
194 # Let empty type delimiter pairs go through.
195 root = command
196 else:
197 # Go backward through the command until we hit an "invalid" character.
198 i = len(command)
199 while i and command[i-1] in validChars:
200 i -= 1
201 # Default to everything from the "invalid" character to the end.
202 root = command[i:]
203 # Override certain exceptions.
204 if i > 0 and command[i-1] in ("'", '"'):
205 # Detect situations where we are in the middle of a string.
206 # This code catches the simplest case, but needs to catch others.
207 root = ''
208 elif ((2 <= i < len(command) and command[i] == '.') \
209 or (2 <= i <= len(command) and terminator in ('.', '', None))) \
210 and command[i-2:i] in emptyTypes:
211 # Allow empty types to get through.
212 # Don't confuse an empty tupple with an argumentless callable.
213 if i == 2 or (i >= 3 and command[i-3] in validSeparators):
214 root = command[i-2:]
215 return root
216
217 def rtrimTerminus(command, terminator=None):
218 """Return command minus the final terminator and anything that follows."""
219 if terminator:
220 pieces = command.split(terminator)
221 if len(pieces) > 1:
222 command = terminator.join(pieces[:-1])
223 return command
224
225 def getBaseObject(object):
226 """Return base object and dropSelf indicator for an object."""
227 if inspect.isbuiltin(object):
228 # Builtin functions don't have an argspec that we can get.
229 dropSelf = 0
230 elif inspect.ismethod(object):
231 # Get the function from the object otherwise inspect.getargspec()
232 # complains that the object isn't a Python function.
233 try:
234 if object.im_self is None:
235 # This is an unbound method so we do not drop self from the
236 # argspec, since an instance must be passed as the first arg.
237 dropSelf = 0
238 else:
239 dropSelf = 1
240 object = object.im_func
241 except AttributeError:
242 dropSelf = 0
243 elif inspect.isclass(object):
244 # Get the __init__ method function for the class.
245 constructor = getConstructor(object)
246 if constructor is not None:
247 object = constructor
248 dropSelf = 1
249 else:
250 dropSelf = 0
251 elif callable(object):
252 # Get the __call__ method instead.
253 try:
254 object = object.__call__.im_func
255 dropSelf = 1
256 except AttributeError:
257 dropSelf = 0
258 else:
259 dropSelf = 0
260 return object, dropSelf
261
262 def getConstructor(object):
263 """Return constructor for class object, or None if there isn't one."""
264 try:
265 return object.__init__.im_func
266 except AttributeError:
267 for base in object.__bases__:
268 constructor = getConstructor(base)
269 if constructor is not None:
270 return constructor
271 return None
272
273
274