mirror of
https://github.com/spl0k/supysonic.git
synced 2024-11-14 22:22:18 +00:00
179 lines
5.9 KiB
Python
179 lines
5.9 KiB
Python
|
# This file is part of beets.
|
||
|
# Copyright 2013, Adrian Sampson.
|
||
|
#
|
||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||
|
# a copy of this software and associated documentation files (the
|
||
|
# "Software"), to deal in the Software without restriction, including
|
||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||
|
# the following conditions:
|
||
|
#
|
||
|
# The above copyright notice and this permission notice shall be
|
||
|
# included in all copies or substantial portions of the Software.
|
||
|
|
||
|
"""A metaclass for enumerated types that really are types.
|
||
|
|
||
|
You can create enumerations with `enum(values, [name])` and they work
|
||
|
how you would expect them to.
|
||
|
|
||
|
>>> from enumeration import enum
|
||
|
>>> Direction = enum('north east south west', name='Direction')
|
||
|
>>> Direction.west
|
||
|
Direction.west
|
||
|
>>> Direction.west == Direction.west
|
||
|
True
|
||
|
>>> Direction.west == Direction.east
|
||
|
False
|
||
|
>>> isinstance(Direction.west, Direction)
|
||
|
True
|
||
|
>>> Direction[3]
|
||
|
Direction.west
|
||
|
>>> Direction['west']
|
||
|
Direction.west
|
||
|
>>> Direction.west.name
|
||
|
'west'
|
||
|
>>> Direction.north < Direction.west
|
||
|
True
|
||
|
|
||
|
Enumerations are classes; their instances represent the possible values
|
||
|
of the enumeration. Because Python classes must have names, you may
|
||
|
provide a `name` parameter to `enum`; if you don't, a meaningless one
|
||
|
will be chosen for you.
|
||
|
"""
|
||
|
import random
|
||
|
|
||
|
class Enumeration(type):
|
||
|
"""A metaclass whose classes are enumerations.
|
||
|
|
||
|
The `values` attribute of the class is used to populate the
|
||
|
enumeration. Values may either be a list of enumerated names or a
|
||
|
string containing a space-separated list of names. When the class
|
||
|
is created, it is instantiated for each name value in `values`.
|
||
|
Each such instance is the name of the enumerated item as the sole
|
||
|
argument.
|
||
|
|
||
|
The `Enumerated` class is a good choice for a superclass.
|
||
|
"""
|
||
|
|
||
|
def __init__(cls, name, bases, dic):
|
||
|
super(Enumeration, cls).__init__(name, bases, dic)
|
||
|
|
||
|
if 'values' not in dic:
|
||
|
# Do nothing if no values are provided (i.e., with
|
||
|
# Enumerated itself).
|
||
|
return
|
||
|
|
||
|
# May be called with a single string, in which case we split on
|
||
|
# whitespace for convenience.
|
||
|
values = dic['values']
|
||
|
if isinstance(values, basestring):
|
||
|
values = values.split()
|
||
|
|
||
|
# Create the Enumerated instances for each value. We have to use
|
||
|
# super's __setattr__ here because we disallow setattr below.
|
||
|
super(Enumeration, cls).__setattr__('_items_dict', {})
|
||
|
super(Enumeration, cls).__setattr__('_items_list', [])
|
||
|
for value in values:
|
||
|
item = cls(value, len(cls._items_list))
|
||
|
cls._items_dict[value] = item
|
||
|
cls._items_list.append(item)
|
||
|
|
||
|
def __getattr__(cls, key):
|
||
|
try:
|
||
|
return cls._items_dict[key]
|
||
|
except KeyError:
|
||
|
raise AttributeError("enumeration '" + cls.__name__ +
|
||
|
"' has no item '" + key + "'")
|
||
|
|
||
|
def __setattr__(cls, key, val):
|
||
|
raise TypeError("enumerations do not support attribute assignment")
|
||
|
|
||
|
def __getitem__(cls, key):
|
||
|
if isinstance(key, int):
|
||
|
return cls._items_list[key]
|
||
|
else:
|
||
|
return getattr(cls, key)
|
||
|
|
||
|
def __len__(cls):
|
||
|
return len(cls._items_list)
|
||
|
|
||
|
def __iter__(cls):
|
||
|
return iter(cls._items_list)
|
||
|
|
||
|
def __nonzero__(cls):
|
||
|
# Ensures that __len__ doesn't get called before __init__ by
|
||
|
# pydoc.
|
||
|
return True
|
||
|
|
||
|
class Enumerated(object):
|
||
|
"""An item in an enumeration.
|
||
|
|
||
|
Contains instance methods inherited by enumerated objects. The
|
||
|
metaclass is preset to `Enumeration` for your convenience.
|
||
|
|
||
|
Instance attributes:
|
||
|
name -- The name of the item.
|
||
|
index -- The index of the item in its enumeration.
|
||
|
|
||
|
>>> from enumeration import Enumerated
|
||
|
>>> class Garment(Enumerated):
|
||
|
... values = 'hat glove belt poncho lederhosen suspenders'
|
||
|
... def wear(self):
|
||
|
... print('now wearing a ' + self.name)
|
||
|
...
|
||
|
>>> Garment.poncho.wear()
|
||
|
now wearing a poncho
|
||
|
"""
|
||
|
|
||
|
__metaclass__ = Enumeration
|
||
|
|
||
|
def __init__(self, name, index):
|
||
|
self.name = name
|
||
|
self.index = index
|
||
|
|
||
|
def __str__(self):
|
||
|
return type(self).__name__ + '.' + self.name
|
||
|
|
||
|
def __repr__(self):
|
||
|
return str(self)
|
||
|
|
||
|
def __cmp__(self, other):
|
||
|
if type(self) is type(other):
|
||
|
# Note that we're assuming that the items are direct
|
||
|
# instances of the same Enumeration (i.e., no fancy
|
||
|
# subclassing), which is probably okay.
|
||
|
return cmp(self.index, other.index)
|
||
|
else:
|
||
|
return NotImplemented
|
||
|
|
||
|
def enum(*values, **kwargs):
|
||
|
"""Shorthand for creating a new Enumeration class.
|
||
|
|
||
|
Call with enumeration values as a list, a space-delimited string, or
|
||
|
just an argument list. To give the class a name, pass it as the
|
||
|
`name` keyword argument. Otherwise, a name will be chosen for you.
|
||
|
|
||
|
The following are all equivalent:
|
||
|
|
||
|
enum('pinkie ring middle index thumb')
|
||
|
enum('pinkie', 'ring', 'middle', 'index', 'thumb')
|
||
|
enum(['pinkie', 'ring', 'middle', 'index', 'thumb'])
|
||
|
"""
|
||
|
|
||
|
if ('name' not in kwargs) or kwargs['name'] is None:
|
||
|
# Create a probably-unique name. It doesn't really have to be
|
||
|
# unique, but getting distinct names each time helps with
|
||
|
# identification in debugging.
|
||
|
name = 'Enumeration' + hex(random.randint(0,0xfffffff))[2:].upper()
|
||
|
else:
|
||
|
name = kwargs['name']
|
||
|
|
||
|
if len(values) == 1:
|
||
|
# If there's only one value, we have a couple of alternate calling
|
||
|
# styles.
|
||
|
if isinstance(values[0], basestring) or hasattr(values[0], '__iter__'):
|
||
|
values = values[0]
|
||
|
|
||
|
return type(name, (Enumerated,), {'values': values})
|