"""$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/grouch/lib/util.py $
$Id: util.py 24750 2004-07-21 15:26:51Z dbinger $
"""

import sys
import __builtin__
from types import TypeType, ClassType, InstanceType


def is_class_object (klass):
    """Return true if 'klass' is a class object (either a regular Python
    class or an ExtensionClass).
    """
    # The first clause handles regular classes quickly; the second
    # is needed to accomodate ExtensionClasses
    return type(klass) is ClassType or hasattr(klass, '__bases__')

def is_instance_object (object):
    """Return true if 'object' is an instance of some class (either a
    regular Python class or an ExtensionClass).
    """
    return ((type(object) is InstanceType or
             (hasattr(object, '__class__') and
              not hasattr(object, '__bases__'))) and
            hasattr(object, '__dict__'))

def get_class_object (name):
    lastdot = name.rfind(".")
    if lastdot == -1:                   # no dots -- look in main module
        module_name = '__main__'
        klass_name = name
        module = sys.modules[module_name]
    else:
        module_name = name[:lastdot]
        klass_name = name[lastdot+1:]
        try:
            __import__(module_name)
        except ImportError, exc:
            raise ValueError, \
                  "error finding class %s: %s" % (name, exc)
        module = sys.modules[module_name]

    if not hasattr(module, klass_name):
        raise ValueError, \
              "no such class %s in module %s" % (`klass_name`, module_name)
    else:
        klass = getattr(module, klass_name)
        if is_class_object(klass):
            return klass
        else:
            raise ValueError, \
                  "%s in module %s: not a class" % (`klass_name`, module_name)


def get_full_classname (klass):
    classname = klass.__name__
    modname = klass.__module__
    if modname == "__main__":
        return classname
    else:
        return "%s.%s" % (modname, classname)


# Supposedly, these two won't be necessary with Python 2.1, since its
# 'issubclass()' and 'isinstance()' will finally be able to deal with
# extension classes completely in 2.1.  (It's close in 1.6 and 2.0, but not
# quite there yet.)  I have not yet verified this!

def issubclass (class1, class2):
    """A version of 'issubclass()' that works with extension classes
    as well as regular Python classes.
    """

    # Both class objects are regular Python classes, so use the
    # built-in 'issubclass()'.
    if type(class1) is ClassType and type(class2) is ClassType:
        return __builtin__.issubclass(class1, class2)

    # Both so-called class objects have a '__bases__' attribute: ie.,
    # they aren't regular Python classes, but they sure look like them.
    # Assume they are extension classes and reimplement what the builtin
    # 'issubclass()' does behind the scenes.
    elif hasattr(class1, '__bases__') and hasattr(class2, '__bases__'):
        # XXX it appears that "ec.__class__ is type(ec)" for an
        # extension class 'ec': could we/should we use this as an
        # additional check for extension classes?

        # Breadth-first traversal of class1's superclass tree.  Order
        # doesn't matter because we're just looking for a "yes/no"
        # answer from the tree; if we were trying to resolve a name,
        # order would be important!
        stack = [class1]
        while stack:
            if stack[0] is class2:
                return 1
            stack.extend(list(stack[0].__bases__))
            del stack[0]
        else:
            return 0

    # Not a regular class, not an extension class: blow up for consistency
    # with builtin 'issubclass()"
    else:
        raise TypeError, "arguments must be class or ExtensionClass objects"

# issubclass ()

def isinstance (object, klass):
    """A version of 'isinstance()' that works with extension classes
    as well as regular Python classes."""

    if type(klass) is TypeType:
        return __builtin__.isinstance(object, klass)
    elif hasattr(object, '__class__'):
        return issubclass(object.__class__, klass)
    else:
        return 0


import types

# This is the list of types always defined in Python 2.0's types.py.
# Some types (complex, unicode) are optional at build-time; others were
# added in Python 2.2.  Those are handled separately so Grouch will work
# with the lowest-common-denominator Python 2.0.

_type_name = {
    # everyday types
    types.DictType             : 'dict',
    types.FileType             : 'file',
    types.FloatType            : 'float',
    types.IntType              : 'int',
    types.ListType             : 'list',
    types.LongType             : 'long',
    types.NoneType             : 'none',
    types.StringType           : 'string',
    types.TupleType            : 'tuple',

    # callable things
    types.BuiltinFunctionType  : 'builtin function',
    types.FunctionType         : 'function',
    types.MethodType           : 'instance method',

    # not-quite-everyday types
    types.ClassType            : 'class',
    types.InstanceType         : 'instance',
    types.ModuleType           : 'module',
    types.TypeType             : 'type',

    # esoterica
    types.CodeType             : 'code',
    types.FrameType            : 'frame',
    types.TracebackType        : 'traceback',
    types.BufferType           : 'buffer',
    types.EllipsisType         : 'ellipsis',
    types.SliceType            : 'slice',
    types.XRangeType           : 'xrange',
}

# These are optional in Python 2.0, or were added in 2.2.
for (name, typename) in [("ComplexType", "complex"),
                         ("UnicodeType", "unicode"),
                         ("DictProxyType", "dict-proxy"),
                         ("GeneratorType", "generator"),
                         ("ObjectType", "object")]:
    if hasattr(types, name):
        _type_name[getattr(types, name)] = typename

def get_type_name (type):
    global _type_name
    return _type_name.get(type, type.__name__)
