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