git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@33055 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			383 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			383 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """Provides a variety of introspective-type support functions for
 | |
| things like call tips and command auto completion."""
 | |
| 
 | |
| __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
 | |
| __cvsid__ = "$Id$"
 | |
| __revision__ = "$Revision$"[11:-2]
 | |
| 
 | |
| from __future__ import nested_scopes
 | |
| 
 | |
| import cStringIO
 | |
| import inspect
 | |
| import sys
 | |
| import tokenize
 | |
| import types
 | |
| import wx
 | |
| 
 | |
| 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.
 | |
|         special_attrs = ['__bases__', '__class__', '__dict__', '__name__',
 | |
|                          'func_closure', 'func_code', 'func_defaults',
 | |
|                          'func_dict', 'func_doc', 'func_globals', 'func_name']
 | |
|         attributes += [attr for attr in special_attrs \
 | |
|                        if hasattr(object, attr)]
 | |
|     if includeMagic:
 | |
|         try: attributes += object._getAttributeNames()
 | |
|         except: pass
 | |
|     # Get all attribute names.
 | |
|     str_type = str(type(object))
 | |
|     if str_type == "<type 'array'>":
 | |
|         attributes += dir(object)
 | |
|     else:
 | |
|         attrdict = getAllAttributeNames(object)
 | |
|         # Store the object's dir.
 | |
|         object_dir = dir(object)
 | |
|         for (obj_type_name, technique, count), attrlist in attrdict.items():
 | |
|             # This complexity is necessary to avoid accessing all the
 | |
|             # attributes of the object.  This is very handy for objects
 | |
|             # whose attributes are lazily evaluated.
 | |
|             if type(object).__name__ == obj_type_name and technique == 'dir':
 | |
|                 attributes += attrlist
 | |
|             else:
 | |
|                 attributes += [attr for attr in attrlist \
 | |
|                                if attr not in object_dir and hasattr(object, attr)]
 | |
|             
 | |
|     # Remove duplicates from the attribute list.
 | |
|     for item in attributes:
 | |
|         dict[item] = None
 | |
|     attributes = dict.keys()
 | |
|     # new-style swig wrappings can result in non-string attributes
 | |
|     # e.g. ITK http://www.itk.org/
 | |
|     attributes = [attribute for attribute in attributes \
 | |
|                   if type(attribute) == str]
 | |
|     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)
 | |
|     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:
 | |
|         # This could(?) fail if the type is poorly defined without
 | |
|         # even a name.
 | |
|         key = type(object).__name__
 | |
|     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 = '()'
 | |
|             elif temp[0][:2] == '(*': # first param is like *args, not self
 | |
|                 pass 
 | |
|             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 or firstline[:len(name)+1] == name+'(':
 | |
|             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."""
 | |
| 
 | |
|     # In case the command is unicode try encoding it
 | |
|     if type(command) == unicode:
 | |
|         try:
 | |
|             command = command.encode(wx.GetDefaultPyEncoding())
 | |
|         except UnicodeEncodeError:
 | |
|             pass # otherwise leave it alone
 | |
|                 
 | |
|     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
 |