
"""
ConstantMap - generate inverted maps for files of magic numbers

You occasionally encounter Python modules that are little more than
collections of constants.  The now defunct ERRNO.py is such a module.  It
looks like

    # Generated by h2py from /usr/include/sys/errno.h

    # Included from standards.h
    __KBASE = 1000
    __IRIXBASE = 1000
    EPERM = 1
    ENOENT = 2
    ESRCH = 3
    EINTR = 4
    EIO = 5
    ...

Such modules are convenient because you can generate them automatically
using h2py and not have to maintain them manually.  Unfortunately, it's
tedious to interpret these magic numbers when debugging, as all you have is
a number.  The ConstantMap class solves this problem by making it easy to
generate an inverted map that maps the constant values to their names.

Consider a file that contains constants representing some common names and
colors:

    import ConstantMap
    mapper = ConstantMap.ConstantMap()

    Sally = 0
    Bruce = 1
    Peggy = 2
    Marty = 3
    NameMap = mapper.make(globals())

    Cyan, Magenta, Yellow = range(3)
    ColorMap = mapper.make(globals())

    print NameMap(0)
    print ColorMap(2)

    del ConstantMap, mapper

The output of executing this script would be

    Sally
    Yellow

When ConstantMap is instantiated it creates an empty map.  Each time its
make() method is called it is passed a current copy of globals().  It
creates a new ConstantMap instance which contains any symbols it finds in
globals() that are not in its map.  It then updates itself from the passed
in globals() and returns the new instance.

"""

class ConstantMap:
    def __init__(self, markdict=None):
        """add new global symbols to instance's name map"""
        self.names = {}
        self.markdict = markdict or {}

    def __call__(self, key):
        """return a specific name mapping"""
        return self.names[key]

    def clear():
        """clear the mark dictionary"""
        self.markdict.clear()

    def make(self, globals):
        """generate and return a new map
        
        the new map contains symbols added since the last call to make
        symbols starting with '__' or values that are modules, instances or
        classes are not added to the map.
        """
        import types
        newmapper = ConstantMap(self.markdict)
        for key,val in globals.items():
            if (not newmapper.markdict.has_key(key) and
                key[:2] != "__" and
                type(val) not in [types.ModuleType, types.InstanceType,
                                  types.ClassType]):
                newmapper.names[val] = key
        self.markdict.update(globals)
        return newmapper
