Merged the wxPy_newswig branch into the HEAD branch (main trunk)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@24541 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -1,8 +1,363 @@
|
||||
"""Provides a variety of introspective-type support functions for
|
||||
things like call tips and command auto completion."""
|
||||
|
||||
"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
|
||||
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
|
||||
__cvsid__ = "$Id$"
|
||||
__revision__ = "$Revision$"[11:-2]
|
||||
|
||||
from wx import _rename
|
||||
from wxPython.py import introspect
|
||||
_rename(globals(), introspect.__dict__, modulename='py.introspect')
|
||||
del introspect
|
||||
del _rename
|
||||
from __future__ import nested_scopes
|
||||
|
||||
import cStringIO
|
||||
import inspect
|
||||
import sys
|
||||
import tokenize
|
||||
import types
|
||||
|
||||
try:
|
||||
True
|
||||
except NameError:
|
||||
True = 1==1
|
||||
False = 1==0
|
||||
|
||||
def getAutoCompleteList(command='', locals=None, includeMagic=1,
|
||||
includeSingle=1, includeDouble=1):
|
||||
"""Return list of auto-completion options for command.
|
||||
|
||||
The list of options will be based on the locals namespace."""
|
||||
attributes = []
|
||||
# Get the proper chunk of code from the command.
|
||||
root = getRoot(command, terminator='.')
|
||||
try:
|
||||
if locals is not None:
|
||||
object = eval(root, locals)
|
||||
else:
|
||||
object = eval(root)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
attributes = getAttributeNames(object, includeMagic,
|
||||
includeSingle, includeDouble)
|
||||
return attributes
|
||||
|
||||
def getAttributeNames(object, includeMagic=1, includeSingle=1,
|
||||
includeDouble=1):
|
||||
"""Return list of unique attributes, including inherited, for object."""
|
||||
attributes = []
|
||||
dict = {}
|
||||
if not hasattrAlwaysReturnsTrue(object):
|
||||
# Add some attributes that don't always get picked up. If
|
||||
# they don't apply, they'll get filtered out at the end.
|
||||
attributes += ['__bases__', '__class__', '__dict__', '__name__',
|
||||
'func_closure', 'func_code', 'func_defaults',
|
||||
'func_dict', 'func_doc', 'func_globals', 'func_name']
|
||||
if includeMagic:
|
||||
try: attributes += object._getAttributeNames()
|
||||
except: pass
|
||||
# Get all attribute names.
|
||||
attrdict = getAllAttributeNames(object)
|
||||
for attrlist in attrdict.values():
|
||||
attributes += attrlist
|
||||
# Remove duplicates from the attribute list.
|
||||
for item in attributes:
|
||||
dict[item] = None
|
||||
attributes = dict.keys()
|
||||
attributes.sort(lambda x, y: cmp(x.upper(), y.upper()))
|
||||
if not includeSingle:
|
||||
attributes = filter(lambda item: item[0]!='_' \
|
||||
or item[1]=='_', attributes)
|
||||
if not includeDouble:
|
||||
attributes = filter(lambda item: item[:2]!='__', attributes)
|
||||
# Make sure we haven't picked up any bogus attributes somehow.
|
||||
attributes = [attribute for attribute in attributes \
|
||||
if hasattr(object, attribute)]
|
||||
return attributes
|
||||
|
||||
def hasattrAlwaysReturnsTrue(object):
|
||||
return hasattr(object, 'bogu5_123_aTTri8ute')
|
||||
|
||||
def getAllAttributeNames(object):
|
||||
"""Return dict of all attributes, including inherited, for an object.
|
||||
|
||||
Recursively walk through a class and all base classes.
|
||||
"""
|
||||
attrdict = {} # (object, technique, count): [list of attributes]
|
||||
# !!!
|
||||
# Do Not use hasattr() as a test anywhere in this function,
|
||||
# because it is unreliable with remote objects: xmlrpc, soap, etc.
|
||||
# They always return true for hasattr().
|
||||
# !!!
|
||||
try:
|
||||
# Yes, this can fail if object is an instance of a class with
|
||||
# __str__ (or __repr__) having a bug or raising an
|
||||
# exception. :-(
|
||||
key = str(object)
|
||||
except:
|
||||
key = 'anonymous'
|
||||
# Wake up sleepy objects - a hack for ZODB objects in "ghost" state.
|
||||
wakeupcall = dir(object)
|
||||
del wakeupcall
|
||||
# Get attributes available through the normal convention.
|
||||
attributes = dir(object)
|
||||
attrdict[(key, 'dir', len(attributes))] = attributes
|
||||
# Get attributes from the object's dictionary, if it has one.
|
||||
try:
|
||||
attributes = object.__dict__.keys()
|
||||
attributes.sort()
|
||||
except: # Must catch all because object might have __getattr__.
|
||||
pass
|
||||
else:
|
||||
attrdict[(key, '__dict__', len(attributes))] = attributes
|
||||
# For a class instance, get the attributes for the class.
|
||||
try:
|
||||
klass = object.__class__
|
||||
except: # Must catch all because object might have __getattr__.
|
||||
pass
|
||||
else:
|
||||
if klass is object:
|
||||
# Break a circular reference. This happens with extension
|
||||
# classes.
|
||||
pass
|
||||
else:
|
||||
attrdict.update(getAllAttributeNames(klass))
|
||||
# Also get attributes from any and all parent classes.
|
||||
try:
|
||||
bases = object.__bases__
|
||||
except: # Must catch all because object might have __getattr__.
|
||||
pass
|
||||
else:
|
||||
if isinstance(bases, types.TupleType):
|
||||
for base in bases:
|
||||
if type(base) is types.TypeType:
|
||||
# Break a circular reference. Happens in Python 2.2.
|
||||
pass
|
||||
else:
|
||||
attrdict.update(getAllAttributeNames(base))
|
||||
return attrdict
|
||||
|
||||
def getCallTip(command='', locals=None):
|
||||
"""For a command, return a tuple of object name, argspec, tip text.
|
||||
|
||||
The call tip information will be based on the locals namespace."""
|
||||
calltip = ('', '', '') # object name, argspec, tip text.
|
||||
# Get the proper chunk of code from the command.
|
||||
root = getRoot(command, terminator='(')
|
||||
try:
|
||||
if locals is not None:
|
||||
object = eval(root, locals)
|
||||
else:
|
||||
object = eval(root)
|
||||
except:
|
||||
return calltip
|
||||
name = ''
|
||||
object, dropSelf = getBaseObject(object)
|
||||
try:
|
||||
name = object.__name__
|
||||
except AttributeError:
|
||||
pass
|
||||
tip1 = ''
|
||||
argspec = ''
|
||||
if inspect.isbuiltin(object):
|
||||
# Builtin functions don't have an argspec that we can get.
|
||||
pass
|
||||
elif inspect.isfunction(object):
|
||||
# tip1 is a string like: "getCallTip(command='', locals=None)"
|
||||
argspec = apply(inspect.formatargspec, inspect.getargspec(object))
|
||||
if dropSelf:
|
||||
# The first parameter to a method is a reference to an
|
||||
# instance, usually coded as "self", and is usually passed
|
||||
# automatically by Python; therefore we want to drop it.
|
||||
temp = argspec.split(',')
|
||||
if len(temp) == 1: # No other arguments.
|
||||
argspec = '()'
|
||||
else: # Drop the first argument.
|
||||
argspec = '(' + ','.join(temp[1:]).lstrip()
|
||||
tip1 = name + argspec
|
||||
doc = ''
|
||||
if callable(object):
|
||||
try:
|
||||
doc = inspect.getdoc(object)
|
||||
except:
|
||||
pass
|
||||
if doc:
|
||||
# tip2 is the first separated line of the docstring, like:
|
||||
# "Return call tip text for a command."
|
||||
# tip3 is the rest of the docstring, like:
|
||||
# "The call tip information will be based on ... <snip>
|
||||
firstline = doc.split('\n')[0].lstrip()
|
||||
if tip1 == firstline:
|
||||
tip1 = ''
|
||||
else:
|
||||
tip1 += '\n\n'
|
||||
docpieces = doc.split('\n\n')
|
||||
tip2 = docpieces[0]
|
||||
tip3 = '\n\n'.join(docpieces[1:])
|
||||
tip = '%s%s\n\n%s' % (tip1, tip2, tip3)
|
||||
else:
|
||||
tip = tip1
|
||||
calltip = (name, argspec[1:-1], tip.strip())
|
||||
return calltip
|
||||
|
||||
def getRoot(command, terminator=None):
|
||||
"""Return the rightmost root portion of an arbitrary Python command.
|
||||
|
||||
Return only the root portion that can be eval()'d without side
|
||||
effects. The command would normally terminate with a '(' or
|
||||
'.'. The terminator and anything after the terminator will be
|
||||
dropped."""
|
||||
command = command.split('\n')[-1]
|
||||
if command.startswith(sys.ps2):
|
||||
command = command[len(sys.ps2):]
|
||||
command = command.lstrip()
|
||||
command = rtrimTerminus(command, terminator)
|
||||
tokens = getTokens(command)
|
||||
if not tokens:
|
||||
return ''
|
||||
if tokens[-1][0] is tokenize.ENDMARKER:
|
||||
# Remove the end marker.
|
||||
del tokens[-1]
|
||||
if not tokens:
|
||||
return ''
|
||||
if terminator == '.' and \
|
||||
(tokens[-1][1] <> '.' or tokens[-1][0] is not tokenize.OP):
|
||||
# Trap decimals in numbers, versus the dot operator.
|
||||
return ''
|
||||
else:
|
||||
# Strip off the terminator.
|
||||
if terminator and command.endswith(terminator):
|
||||
size = 0 - len(terminator)
|
||||
command = command[:size]
|
||||
command = command.rstrip()
|
||||
tokens = getTokens(command)
|
||||
tokens.reverse()
|
||||
line = ''
|
||||
start = None
|
||||
prefix = ''
|
||||
laststring = '.'
|
||||
emptyTypes = ('[]', '()', '{}')
|
||||
for token in tokens:
|
||||
tokentype = token[0]
|
||||
tokenstring = token[1]
|
||||
line = token[4]
|
||||
if tokentype is tokenize.ENDMARKER:
|
||||
continue
|
||||
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
|
||||
and laststring != '.':
|
||||
# We've reached something that's not part of the root.
|
||||
if prefix and line[token[3][1]] != ' ':
|
||||
# If it doesn't have a space after it, remove the prefix.
|
||||
prefix = ''
|
||||
break
|
||||
if tokentype in (tokenize.NAME, tokenize.STRING, tokenize.NUMBER) \
|
||||
or (tokentype is tokenize.OP and tokenstring == '.'):
|
||||
if prefix:
|
||||
# The prefix isn't valid because it comes after a dot.
|
||||
prefix = ''
|
||||
break
|
||||
else:
|
||||
# start represents the last known good point in the line.
|
||||
start = token[2][1]
|
||||
elif len(tokenstring) == 1 and tokenstring in ('[({])}'):
|
||||
# Remember, we're working backwords.
|
||||
# So prefix += tokenstring would be wrong.
|
||||
if prefix in emptyTypes and tokenstring in ('[({'):
|
||||
# We've already got an empty type identified so now we
|
||||
# are in a nested situation and we can break out with
|
||||
# what we've got.
|
||||
break
|
||||
else:
|
||||
prefix = tokenstring + prefix
|
||||
else:
|
||||
# We've reached something that's not part of the root.
|
||||
break
|
||||
laststring = tokenstring
|
||||
if start is None:
|
||||
start = len(line)
|
||||
root = line[start:]
|
||||
if prefix in emptyTypes:
|
||||
# Empty types are safe to be eval()'d and introspected.
|
||||
root = prefix + root
|
||||
return root
|
||||
|
||||
def getTokens(command):
|
||||
"""Return list of token tuples for command."""
|
||||
command = str(command) # In case the command is unicode, which fails.
|
||||
f = cStringIO.StringIO(command)
|
||||
# tokens is a list of token tuples, each looking like:
|
||||
# (type, string, (srow, scol), (erow, ecol), line)
|
||||
tokens = []
|
||||
# Can't use list comprehension:
|
||||
# tokens = [token for token in tokenize.generate_tokens(f.readline)]
|
||||
# because of need to append as much as possible before TokenError.
|
||||
try:
|
||||
## This code wasn't backward compatible with Python 2.1.3.
|
||||
##
|
||||
## for token in tokenize.generate_tokens(f.readline):
|
||||
## tokens.append(token)
|
||||
|
||||
# This works with Python 2.1.3 (with nested_scopes).
|
||||
def eater(*args):
|
||||
tokens.append(args)
|
||||
tokenize.tokenize_loop(f.readline, eater)
|
||||
except tokenize.TokenError:
|
||||
# This is due to a premature EOF, which we expect since we are
|
||||
# feeding in fragments of Python code.
|
||||
pass
|
||||
return tokens
|
||||
|
||||
def rtrimTerminus(command, terminator=None):
|
||||
"""Return command minus anything that follows the final terminator."""
|
||||
if terminator:
|
||||
pieces = command.split(terminator)
|
||||
if len(pieces) > 1:
|
||||
command = terminator.join(pieces[:-1]) + terminator
|
||||
return command
|
||||
|
||||
def getBaseObject(object):
|
||||
"""Return base object and dropSelf indicator for an object."""
|
||||
if inspect.isbuiltin(object):
|
||||
# Builtin functions don't have an argspec that we can get.
|
||||
dropSelf = 0
|
||||
elif inspect.ismethod(object):
|
||||
# Get the function from the object otherwise
|
||||
# inspect.getargspec() complains that the object isn't a
|
||||
# Python function.
|
||||
try:
|
||||
if object.im_self is None:
|
||||
# This is an unbound method so we do not drop self
|
||||
# from the argspec, since an instance must be passed
|
||||
# as the first arg.
|
||||
dropSelf = 0
|
||||
else:
|
||||
dropSelf = 1
|
||||
object = object.im_func
|
||||
except AttributeError:
|
||||
dropSelf = 0
|
||||
elif inspect.isclass(object):
|
||||
# Get the __init__ method function for the class.
|
||||
constructor = getConstructor(object)
|
||||
if constructor is not None:
|
||||
object = constructor
|
||||
dropSelf = 1
|
||||
else:
|
||||
dropSelf = 0
|
||||
elif callable(object):
|
||||
# Get the __call__ method instead.
|
||||
try:
|
||||
object = object.__call__.im_func
|
||||
dropSelf = 1
|
||||
except AttributeError:
|
||||
dropSelf = 0
|
||||
else:
|
||||
dropSelf = 0
|
||||
return object, dropSelf
|
||||
|
||||
def getConstructor(object):
|
||||
"""Return constructor for class object, or None if there isn't one."""
|
||||
try:
|
||||
return object.__init__.im_func
|
||||
except AttributeError:
|
||||
for base in object.__bases__:
|
||||
constructor = getConstructor(base)
|
||||
if constructor is not None:
|
||||
return constructor
|
||||
return None
|
||||
|
Reference in New Issue
Block a user