mrv.enum
Covered: 239 lines
Missed: 4 lines
Skipped 98 lines
Percent: 98 %
  2
"""This module is designed to be the equivalent of the enum type in other
  3
languages. An enumeration object is created at run time, and contains
  4
named members that are the enumeration elements.
  6
The enumeration is also a tuple of all of the values in it. You can iterate
  7
through the values, perform 'in' tests, slicing, etc. It also includes
  8
functions to lookup specific values by name, or names by value.
 10
You can specify your own element values, or use the create factory method
 11
to create default Elements. These elements are unique system wide, and are
 12
ordered based on the order of the elements in the enumeration. They also
 13
are _repr_'d by the name of the element, which is convenient for testing,
 14
debugging, and generation text output.
 16
Example Code:
 17
	>>>	# Using Element values
 18
	>>> Colors = Enumeration.create('red', 'green', 'blue')
 20
	>>> # Using explicitly specified values
 21
	>>>Borders = Enumeration.create(('SUNKEN', 1),
 22
	>>>							 	('RAISED', 32),
 23
	>>>							 	('FLAT', 2))
 25
	>>> x = Colors.red
 26
	>>> y = Colors.blue
 28
	>>> assert x < y
 29
	>>> assert x == Colors('red')
 30
	>>> assert Borders.FLAT == 2:
 31
	>>> assert 1 in Borders
 33
:note: slightly modified by Sebastian Thiel to be more flexible and suitable as
 34
	base class
 35
"""
 36
__docformat__ = "restructuredtext"
 37
__contact__='garret at bgb dot cc'
 38
__license__='freeware'
 40
import platform
 42
__all__ = ("Element", "Enumeration", "create")
 44
class Element(object):
 45
	"""Internal helper class used to represent an ordered abstract value.
 47
	The values have string representations, have strictly defined ordering
 48
	(inside the set) and are never equal to anything but themselves.
 50
	They are usually created through the create factory method as values
 51
	for Enumerations.
 53
	They assume that the enumeration member will be filled in before any
 54
	comparisons are used. This is done by the Enumeration constructor.
 55
	"""
 57
	def __init__(self, name, value):
 58
		self._name = name
 59
		self._value = value
 60
		self.enumeration = None # Will be filled in later
 62
	def __repr__(self):
 63
		return self._name
 65
	def _checkType( self, other ):
 66
		""":raise TypeError: if other cannot be used with this element"""
 67
		if ( self.__class__ != other.__class__ ) or ( self.enumeration is not other.enumeration ):
 68
			raise TypeError( "%s is incompatible with %s" % ( other, self ) )
 70
	def __cmp__(self, other):
 71
		"""We override cmp only because we want the ordering of elements
 72
		in an enumeration to reflect their position in the enumeration.
 73
		"""
 74
		try:
 75
			self._checkType( other )
 76
		except TypeError:
 77
			return NotImplemented	# to make cmp fail
 81
		return cmp(self._value, other._value)
 83
	def _checkBitflag( self ):
 84
		if not self.enumeration._supports_bitflags:
 85
			raise TypeError( "Enumeration %s of element %s has no bitflag support" % ( self.enumeration, self ) )
 87
	def __or__( self, other ):
 88
		"""Allows oring values together - only works if the values are actually orable
 89
		integer values
 91
		:return: integer with the ored result
 92
		:raise TypeError: if we are not a bitflag or other is not an element of our enumeration"""
 93
		self._checkType( other )
 94
		self._checkBitflag()
 95
		return self.value() | other.value()
 97
	def __xor__( self, other ):
 98
		"""Allows to x-or values together - only works if element's values are xorable
 99
		integer values.
101
		:param other: integer
102
		:return: integer with the xored result"""
103
		self._checkBitflag()
104
		return self.value() ^ other
107
	def __and__( self, other ):
108
		"""Allow and with integers
110
		:return: self if self & other == self or None if our bit is not set in other
111
		:raise TypeError: if other is not an int"""
112
		if not isinstance( other, int ):
113
			raise TypeError( "require integer, got %s" % type( other ) )
115
		if self.value() & other:
116
			return self
118
		return None
120
	def value( self ):
121
		""":return: own value"""
122
		return self._value
124
	def name( self ):
125
		""":return: name of the element"""
126
		return self._name
129
class Enumeration(tuple):
130
	"""This class represents an enumeration. You should not normally create
131
	multiple instances of the same enumeration, instead create one with
132
	however many references are convenient.
134
	The enumeration is a tuple of all of the values in it. You can iterate
135
	through the values, perform 'in' tests, slicing, etc. It also includes
136
	functions to lookup specific values by name, or names by value.
138
	You can specify your own element values, or use the create factory method
139
	to create default Elements. These elements are unique system wide, and are
140
	ordered based on the order of the elements in the enumeration. They also
141
	are _repr_'d by the name of the element, which is convenient for testing,
142
	debugging, and generation text output.
144
	:note: pickling this class with Elements will fail as they contain cyclic
145
		references that it cannot deal with
146
	:todo: implement proper pickle __getstate__ and __setstate__ that deal with
147
		that problem
148
	"""
149
	_slots_ = ( "_nameMap", "_valueMap", "_supports_bitflags" )
151
	def __new__(self, names, values, **kwargs ):
152
		"""This method is needed to get the tuple parent class to do the
153
		Right Thing(tm). """
154
		return tuple.__new__(self, values)
156
	def __setattr__( self, name, value ):
157
		"""Do not allow to change this instance"""
158
		if name in self._slots_:
159
			return super( Enumeration, self ).__setattr__( name, value )
161
		raise AttributeError( "No assignments allowed" )
163
	def __getattr__( self , attr ):
164
		"""Prefer to return value from our value map"""
165
		try:
166
			return self.valueFromName( attr )
167
		except ValueError:
168
			raise AttributeError( "Element %s is not part of the enumeration" % attr )
171
	def __init__(self, names, values, **kwargs ):
172
		"""The arguments needed to construct this class are a list of
173
		element names (which must be unique strings), and element values
174
		(which can be any type of value). If you don't have special needs,
175
		then it's recommended that you use Element instances for the values.
177
		This constructor is normally called through the create factory (which
178
		will create Elements for you), but that is not a requirement.
179
		"""
181
		assert len(names) == len(values)
184
		tuple.__init__(self)
186
		self._nameMap = {}
187
		self._valueMap = {}
188
		self._supports_bitflags = kwargs.get( "_is_bitflag", False )		# insurance for bitflags
191
		for i in xrange(len(names)):
192
			name = names[i]
193
			value = values[i]
196
			if isinstance( value, Element ):
197
				value.enumeration = self
200
			assert not name in self._nameMap
203
			self._nameMap[name] = value
204
			self._valueMap[value] = name
207
	def valueFromName(self, name):
208
		"""Look up the enumeration value for a given element name.
210
		:raise ValueError:"""
211
		try:
212
			return self._nameMap[name]
213
		except KeyError:
214
			raise ValueError("Name %r not found in enumeration, pick one of %s" % (name, ', '.join(str(e) for e in self)))
217
	def nameFromValue(self, value):
218
		"""Look up the name of an enumeration element, given it's value.
220
		If there are multiple elements with the same value, you will only
221
		get a single matching name back. Which name is undefined.
223
		:raise ValueError: if value is not a part of our enumeration"""
224
		try:
225
			return self._valueMap[value]
226
		except KeyError:
227
			raise ValueError("Value %r is not a member of this enumeration" % value)
230
	def _nextOrPrevious( self, element, direction, wrap_around ):
231
		"""do-it method, see `next` and `previous`
233
		:param direction: -1 = previous, 1 = next """
234
		curindex = -1
235
		for i,elm in enumerate( self ):
236
			if elm == element:
237
				curindex = i
238
				break
242
		assert curindex != -1
244
		nextindex = curindex + direction
245
		validnextindex = nextindex
247
		if nextindex >= len( self ):
248
			validnextindex = 0
249
		elif nextindex < 0:
250
			validnextindex = len( self ) - 1
252
		if not wrap_around and ( validnextindex != nextindex ):
253
			raise ValueError( "'%s' has no element in direction %i" % ( element, direction ) )
255
		return self[ validnextindex ]
258
	def next( self, element, wrap_around = False ):
259
		""":return: element following after given element
260
		:param element: element whose successor to return
261
		:param wrap_around: if True, the first Element will be returned if there is
262
			no next element
263
		:raise ValueError: if wrap_around is False and there is no next element"""
264
		return self._nextOrPrevious( element, 1, wrap_around )
266
	def previous( self, element, wrap_around = False ):
267
		""":return: element coming before the given element
268
		:param element: element whose predecessor to return
269
		:param wrap_around: see `next`
270
		:raise ValueError: if wrap_around is False and there is no previous element"""
271
		return self._nextOrPrevious( element, -1, wrap_around )
273
	__call__ = valueFromName
276
def create(*elements, **kwargs ):
277
	"""Factory method for Enumerations. Accepts of list of values that
278
	can either be strings or (name, value) tuple pairs. Strings will
279
	have an Element created for use as their value.
280
	If you provide elements, the member returned when you access the enumeration
281
	will be the element itself.
283
	Example:  Enumeration.create('fred', 'bob', ('joe', 42))
284
	Example:  Enumeration.create('fred', cls = EnumerationSubClass )
285
	Example:  Enumeration.create(Element('fred', Marge), ...)
287
	:param kwargs: 
288
		 * cls: The class to create an enumeration with, must be an instance of Enumeration
289
		 * elmcls: The class to create elements from, must be instance of Element
290
		 * bitflag: if True, default False, the values created will be suitable as bitflags.
291
					This will fail if you passed more items in than supported by the OS ( 32 , 64, etc ) or if
292
					you pass in tuples and thus define the values yourself.
293
	:raise TypeError,ValueError: if bitflags cannot be supported in your case"""
294
	cls = kwargs.pop( "cls", Enumeration )
295
	elmcls = kwargs.pop( "elmcls", Element )
296
	bitflag = kwargs.pop( "bitflag", False )
298
	assert elements
299
	assert Enumeration in cls.mro()
300
	assert Element in elmcls.mro()
303
	if bitflag:
304
		maxbits = int( platform.architecture()[0][:-3] )
305
		if maxbits < len( elements ):
306
			raise ValueError( "You system can only represent %i bits in one integer, %i tried" % ( maxbits, len( elements ) ) )
309
		kwargs[ '_is_bitflag' ] = True
312
	names = list()
313
	values = list()
315
	for element in elements:
317
		if isinstance( element, tuple ):
318
			assert len(element) == 2
319
			if bitflag:
320
				raise TypeError( "If bitflag support is required, tuples are not allowed: %s" % str( element ) )
322
			names.append(element[0])
323
			values.append(element[1])
325
		elif isinstance( element, basestring ):
326
			val = len( names )
327
			if bitflag:
328
				val = 2 ** val
330
			values.append( elmcls( element, val ) )		# zero based ids
331
			names.append(element)
332
		elif isinstance(element, elmcls):
333
			values.append(element)
334
			names.append(element.name())
335
		else:
336
			raise "Unsupported element type: %s" % type( element )
339
	return cls( names, values, **kwargs )