]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/introspect.py
7f4efb64c12f2646487daff4ae6cbfdef4fe1fac
[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 __version__ = "$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
18 # Get the proper chunk of code from the command.
19 root = getRoot(command, terminator='.')
20 try:
21 object = eval(root, locals)
22 attributes = getAttributeNames(object, includeMagic, \
23 includeSingle, includeDouble)
24 return attributes
25 except:
26 return []
27
28 def getAttributeNames(object, includeMagic=1, includeSingle=1, includeDouble=1):
29 """Return list of unique attributes, including inherited, for an object."""
30 attributes = []
31 dict = {}
32 if includeMagic:
33 try: attributes += object._getAttributeNames()
34 except: pass
35 # Get all attribute names, removing duplicates from the attribute list.
36 for item in getAllAttributeNames(object):
37 dict[item] = None
38 attributes += dict.keys()
39 attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
40 if not includeSingle:
41 attributes = filter(lambda item: item[0]!='_' or item[1]=='_', attributes)
42 if not includeDouble:
43 attributes = filter(lambda item: item[:2]!='__', attributes)
44 # Make sure we haven't picked up any bogus attributes somehow.
45 attributes = [attribute for attribute in attributes if hasattr(object, attribute)]
46 return attributes
47
48 def getAllAttributeNames(object):
49 """Return list of all attributes, including inherited, for an object.
50
51 Recursively walk through a class and all base classes.
52 """
53 # !!!
54 # !!! Do Not use hasattr() as a test anywhere in this function,
55 # !!! because it is unreliable with remote objects - xmlrpc, soap, etc.
56 # !!!
57 attributes = []
58 # Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
59 wakeupcall = dir(object)
60 del wakeupcall
61 # Get attributes available through the normal convention.
62 attributes += dir(object)
63 try:
64 keys = object.__dict__.keys()
65 except:
66 pass
67 else:
68 attributes += keys
69 # For a class instance, get the attributes for the class.
70 if hasattr(object, '__class__'):
71 # Break a circular reference. This happens with extension classes.
72 if object.__class__ is object:
73 pass
74 else:
75 attributes += getAllAttributeNames(object.__class__)
76 # Also get attributes from any and all parent classes.
77 try:
78 bases = object.__bases__
79 except:
80 pass
81 else:
82 if isinstance(bases, type(())):
83 for base in bases:
84 if type(base) is types.TypeType:
85 # Break a circular reference. Happens in Python 2.2.
86 pass
87 else:
88 attributes += getAllAttributeNames(base)
89 return attributes
90
91 def getCallTip(command='', locals=None):
92 """Return call tip text for a command.
93
94 The call tip information will be based on the locals namespace."""
95
96 calltip = ('', '', '')
97 # Get the proper chunk of code from the command.
98 root = getRoot(command, terminator='(')
99 try:
100 object = eval(root, locals)
101 except:
102 return calltip
103 name = ''
104 dropSelf = 1
105 # Switch to the object that has the information we need.
106 if inspect.isbuiltin(object):
107 # Builtin functions don't have an argspec that we can get.
108 pass
109 elif inspect.ismethod(object) or hasattr(object, 'im_func'):
110 # Get the function from the object otherwise inspect.getargspec()
111 # complains that the object isn't a Python function.
112 object = object.im_func
113 elif inspect.isclass(object):
114 # Get the __init__ method function for the class.
115 constructor = getConstructor(object)
116 if constructor is not None:
117 object = constructor
118 elif callable(object):
119 # Get the __call__ method instead.
120 try:
121 object = object.__call__.im_func
122 except:
123 dropSelf = 0
124 else:
125 dropSelf = 0
126 if hasattr(object, '__name__'):
127 name = object.__name__
128 tip1 = ''
129 argspec = ''
130 if inspect.isbuiltin(object):
131 # Builtin functions don't have an argspec that we can get.
132 pass
133 elif inspect.isfunction(object):
134 # tip1 is a string like: "getCallTip(command='', locals=None)"
135 argspec = apply(inspect.formatargspec, inspect.getargspec(object))
136 if dropSelf:
137 # The first parameter to a method is a reference to the
138 # instance, usually coded as "self", and is passed
139 # automatically by Python and therefore we want to drop it.
140 temp = argspec.split(',')
141 if len(temp) == 1: # No other arguments.
142 argspec = '()'
143 else: # Drop the first argument.
144 argspec = '(' + ','.join(temp[1:]).lstrip()
145 tip1 = name + argspec
146 doc = inspect.getdoc(object)
147 if doc:
148 # tip2 is the first separated line of the docstring, like:
149 # "Return call tip text for a command."
150 # tip3 is the rest of the docstring, like:
151 # "The call tip information will be based on ... <snip>
152 docpieces = doc.split('\n\n')
153 tip2 = docpieces[0]
154 tip3 = '\n\n'.join(docpieces[1:])
155 tip = '%s\n\n%s\n\n%s' % (tip1, tip2, tip3)
156 else:
157 tip = tip1
158 calltip = (name, argspec[1:-1], tip.strip())
159 return calltip
160
161 def getConstructor(object):
162 """Return constructor for class object, or None if there isn't one."""
163 try:
164 return object.__init__.im_func
165 except AttributeError:
166 for base in object.__bases__:
167 constructor = getConstructor(base)
168 if constructor is not None:
169 return constructor
170 return None
171
172 def getRoot(command, terminator=None):
173 """Return the rightmost root portion of an arbitrary Python command.
174
175 The command would normally terminate with a "(" or ".". Anything after
176 the terminator will be dropped, allowing you to get back to the root.
177 Return only the root portion that can be eval()'d without side effects.
178 """
179 root = ''
180 validChars = "._" + string.uppercase + string.lowercase + string.digits
181 if terminator:
182 # Drop the final terminator and anything that follows.
183 pieces = command.split(terminator)
184 if len(pieces) > 1:
185 command = terminator.join(pieces[:-1])
186 if len(command) == 0:
187 root = ''
188 elif command in ("''", '""', '""""""', '[]', '()', '{}'):
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 # Detect situations where we are in the middle of a string.
197 # This code catches the simplest case, but needs to catch others.
198 if command[i-1] in ("'", '"'):
199 # We're in the middle of a string so we aren't dealing with an
200 # object and it would be misleading to return anything here.
201 root = ''
202 else:
203 # Grab everything from the "invalid" character to the end.
204 root = command[i:]
205 return root
206
207
208