]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/introspect.py
bcb4fa31a1e644ade4dfa8af15535d97a5e27d91
[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 key = str(object)
79 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
80 wakeupcall = dir(object)
81 del wakeupcall
82 # Get attributes available through the normal convention.
83 attributes = dir(object)
84 attrdict[(key, 'dir', len(attributes))] = attributes
85 # Get attributes from the object's dictionary, if it has one.
86 try:
87 attributes = object.__dict__.keys()
88 attributes.sort()
89 except: # Must catch all because object might have __getattr__.
90 pass
91 else:
92 attrdict[(key, '__dict__', len(attributes))] = attributes
93 # For a class instance, get the attributes for the class.
94 try:
95 klass = object.__class__
96 except: # Must catch all because object might have __getattr__.
97 pass
98 else:
99 if klass is object:
100 # Break a circular reference. This happens with extension classes.
101 pass
102 else:
103 attrdict.update(getAllAttributeNames(klass))
104 # Also get attributes from any and all parent classes.
105 try:
106 bases = object.__bases__
107 except: # Must catch all because object might have __getattr__.
108 pass
109 else:
110 if isinstance(bases, types.TupleType):
111 for base in bases:
112 if type(base) is types.TypeType:
113 # Break a circular reference. Happens in Python 2.2.
114 pass
115 else:
116 attrdict.update(getAllAttributeNames(base))
117 return attrdict
118
119 def getCallTip(command='', locals=None):
120 """For a command, return a tuple of object name, argspec, tip text.
121
122 The call tip information will be based on the locals namespace."""
123 calltip = ('', '', '') # object name, argspec, tip text.
124 # Get the proper chunk of code from the command.
125 root = getRoot(command, terminator='(')
126 try:
127 if locals is not None:
128 object = eval(root, locals)
129 else:
130 object = eval(root)
131 except:
132 return calltip
133 name = ''
134 object, dropSelf = getBaseObject(object)
135 try:
136 name = object.__name__
137 except AttributeError:
138 pass
139 tip1 = ''
140 argspec = ''
141 if inspect.isbuiltin(object):
142 # Builtin functions don't have an argspec that we can get.
143 pass
144 elif inspect.isfunction(object):
145 # tip1 is a string like: "getCallTip(command='', locals=None)"
146 argspec = apply(inspect.formatargspec, inspect.getargspec(object))
147 if dropSelf:
148 # The first parameter to a method is a reference to an
149 # instance, usually coded as "self", and is usually passed
150 # automatically by Python and therefore we want to drop it.
151 temp = argspec.split(',')
152 if len(temp) == 1: # No other arguments.
153 argspec = '()'
154 else: # Drop the first argument.
155 argspec = '(' + ','.join(temp[1:]).lstrip()
156 tip1 = name + argspec
157 doc = ''
158 if callable(object):
159 doc = inspect.getdoc(object)
160 if doc:
161 # tip2 is the first separated line of the docstring, like:
162 # "Return call tip text for a command."
163 # tip3 is the rest of the docstring, like:
164 # "The call tip information will be based on ... <snip>
165 docpieces = doc.split('\n\n')
166 tip2 = docpieces[0]
167 tip3 = '\n\n'.join(docpieces[1:])
168 tip = '%s\n\n%s\n\n%s' % (tip1, tip2, tip3)
169 else:
170 tip = tip1
171 calltip = (name, argspec[1:-1], tip.strip())
172 return calltip
173
174 def getRoot(command, terminator=None):
175 """Return the rightmost root portion of an arbitrary Python command.
176
177 Return only the root portion that can be eval()'d without side effects.
178 The command would normally terminate with a "(" or ".". The terminator
179 and anything after the terminator will be dropped."""
180 root = ''
181 validChars = "._" + string.uppercase + string.lowercase + string.digits
182 emptyTypes = ("''", '""', '""""""', "''''''", '[]', '()', '{}')
183 validSeparators = string.whitespace + ',+-*/=%<>&|^~:([{'
184 # Drop the final terminator and anything that follows.
185 command = rtrimTerminus(command, terminator)
186 if len(command) == 0:
187 root = ''
188 elif command in emptyTypes and terminator in ('.', '', None):
189 # Let empty type delimiter pairs go through.
190 root = command
191 else:
192 # Go backward through the command until we hit an "invalid" character.
193 i = len(command)
194 while i and command[i-1] in validChars:
195 i -= 1
196 # Default to everything from the "invalid" character to the end.
197 root = command[i:]
198 # Override certain exceptions.
199 if i > 0 and command[i-1] in ("'", '"'):
200 # Detect situations where we are in the middle of a string.
201 # This code catches the simplest case, but needs to catch others.
202 root = ''
203 elif ((2 <= i < len(command) and command[i] == '.') \
204 or (2 <= i <= len(command) and terminator in ('.', '', None))) \
205 and command[i-2:i] in emptyTypes:
206 # Allow empty types to get through.
207 # Don't confuse an empty tupple with an argumentless callable.
208 if i == 2 or (i >= 3 and command[i-3] in validSeparators):
209 root = command[i-2:]
210 return root
211
212 def rtrimTerminus(command, terminator=None):
213 """Return command minus the final terminator and anything that follows."""
214 if terminator:
215 pieces = command.split(terminator)
216 if len(pieces) > 1:
217 command = terminator.join(pieces[:-1])
218 return command
219
220 def getBaseObject(object):
221 """Return base object and dropSelf indicator for an object."""
222 if inspect.isbuiltin(object):
223 # Builtin functions don't have an argspec that we can get.
224 dropSelf = 0
225 elif inspect.ismethod(object):
226 # Get the function from the object otherwise inspect.getargspec()
227 # complains that the object isn't a Python function.
228 try:
229 if object.im_self is None:
230 # This is an unbound method so we do not drop self from the
231 # argspec, since an instance must be passed as the first arg.
232 dropSelf = 0
233 else:
234 dropSelf = 1
235 object = object.im_func
236 except AttributeError:
237 dropSelf = 0
238 elif inspect.isclass(object):
239 # Get the __init__ method function for the class.
240 constructor = getConstructor(object)
241 if constructor is not None:
242 object = constructor
243 dropSelf = 1
244 else:
245 dropSelf = 0
246 elif callable(object):
247 # Get the __call__ method instead.
248 try:
249 object = object.__call__.im_func
250 dropSelf = 1
251 except AttributeError:
252 dropSelf = 0
253 else:
254 dropSelf = 0
255 return object, dropSelf
256
257 def getConstructor(object):
258 """Return constructor for class object, or None if there isn't one."""
259 try:
260 return object.__init__.im_func
261 except AttributeError:
262 for base in object.__bases__:
263 constructor = getConstructor(base)
264 if constructor is not None:
265 return constructor
266 return None
267
268
269