summaryrefslogtreecommitdiff
path: root/Mailman/enum.py
blob: a42799f370cf546a7c0d5b7be5c2fd1e7349f8c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# Copyright (C) 2004-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

"""Enumeration meta class.

To define your own enumeration, do something like:

>>> class Colors(Enum):
...     red = 1
...     green = 2
...     blue = 3

Enum subclasses cannot be instantiated, but you can convert them to integers
and from integers.  Returned enumeration attributes are singletons and can be
compared by identity only.
"""

COMMASPACE = ', '

# Based on example by Jeremy Hylton
# Modified and extended by Barry Warsaw



class EnumMetaclass(type):
    def __init__(cls, name, bases, dict):
        # cls == the class being defined
        # name == the name of the class
        # bases == the class's bases
        # dict == the class attributes
        super(EnumMetaclass, cls).__init__(name, bases, dict)
        # Store EnumValues here for easy access.
        cls._enums = {}
        # Figure out the set of enum values on the base classes, to ensure
        # that we don't get any duplicate values (which would screw up
        # conversion from integer).
        for basecls in cls.__mro__:
            if hasattr(basecls, '_enums'):
                cls._enums.update(basecls._enums)
        # For each class attribute, create an EnumValue and store that back on
        # the class instead of the int.  Skip Python reserved names.  Also add
        # a mapping from the integer to the instance so we can return the same
        # object on conversion.
        for attr in dict:
            if not (attr.startswith('__') and attr.endswith('__')):
                intval  = dict[attr]
                enumval = EnumValue(name, intval, attr)
                if intval in cls._enums:
                    raise TypeError('Multiple enum values: %s' % enumval)
                # Store as an attribute on the class, and save the attr name
                setattr(cls, attr, enumval)
                cls._enums[intval] = attr

    def __getattr__(cls, name):
        if name == '__members__':
            return cls._enums.values()
        raise AttributeError(name)

    def __repr__(cls):
        enums = ['%s: %d' % (cls._enums[k], k) for k in sorted(cls._enums)]
        return '<%s {%s}>' % (cls.__name__, COMMASPACE.join(enums))

    def __iter__(cls):
        for i in sorted(cls._enums):
            yield cls._enums[i]

    def __getitem__(cls, i):
        # i can be an integer or a string
        attr = cls._enums.get(i)
        if attr is None:
            # It wasn't an integer -- try attribute name
            try:
                return getattr(cls, i)
            except (AttributeError, TypeError):
                raise ValueError(i)
        return getattr(cls, attr)

    # Support both MyEnum[i] and MyEnum(i)
    __call__ = __getitem__



class EnumValue(object):
    """Class to represent an enumeration value.

    EnumValue('Color', 'red', 12) prints as 'Color.red' and can be converted
    to the integer 12.
    """
    def __init__(self, classname, value, enumname):
        self._classname = classname
        self._value     = value
        self._enumname  = enumname

    def __repr__(self):
        return 'EnumValue(%s, %s, %d)' % (
            self._classname, self._enumname, self._value)

    def __str__(self):
        return self._enumname

    def __int__(self):
        return self._value

    # Support only comparison by identity.  Yes, really raise
    # NotImplementedError instead of returning NotImplemented.
    def __eq__(self, other):
        raise NotImplementedError

    __ne__ = __eq__
    __lt__ = __eq__
    __gt__ = __eq__
    __le__ = __eq__
    __ge__ = __eq__



class Enum:
    __metaclass__ = EnumMetaclass