Bug 1062221 - Add a TypedList type and refactor mozbuild.util lists. r=gps

This commit is contained in:
Mike Hommey 2014-10-02 09:14:06 +09:00
parent 276d86f006
commit 61655fba8e
2 changed files with 224 additions and 42 deletions

View File

@ -28,6 +28,7 @@ from mozbuild.util import (
HierarchicalStringListWithFlagsFactory,
StrictOrderingOnAppendList,
StrictOrderingOnAppendListWithFlagsFactory,
TypedList,
UnsortedError,
)
@ -537,5 +538,128 @@ class TestMemoize(unittest.TestCase):
self.assertEqual(instance.wrapped, 42)
self.assertEqual(instance._count, 1)
class TestTypedList(unittest.TestCase):
def test_init(self):
cls = TypedList(int)
l = cls()
self.assertEqual(len(l), 0)
l = cls([1, 2, 3])
self.assertEqual(len(l), 3)
with self.assertRaises(ValueError):
cls([1, 2, 'c'])
def test_extend(self):
cls = TypedList(int)
l = cls()
l.extend([1, 2])
self.assertEqual(len(l), 2)
self.assertIsInstance(l, cls)
with self.assertRaises(ValueError):
l.extend([3, 'c'])
self.assertEqual(len(l), 2)
def test_slicing(self):
cls = TypedList(int)
l = cls()
l[:] = [1, 2]
self.assertEqual(len(l), 2)
self.assertIsInstance(l, cls)
with self.assertRaises(ValueError):
l[:] = [3, 'c']
self.assertEqual(len(l), 2)
def test_add(self):
cls = TypedList(int)
l = cls()
l2 = l + [1, 2]
self.assertEqual(len(l), 0)
self.assertEqual(len(l2), 2)
self.assertIsInstance(l2, cls)
with self.assertRaises(ValueError):
l2 = l + [3, 'c']
self.assertEqual(len(l), 0)
def test_iadd(self):
cls = TypedList(int)
l = cls()
l += [1, 2]
self.assertEqual(len(l), 2)
self.assertIsInstance(l, cls)
with self.assertRaises(ValueError):
l += [3, 'c']
self.assertEqual(len(l), 2)
def test_add_coercion(self):
objs = []
class Foo(object):
def __init__(self, obj):
objs.append(obj)
cls = TypedList(Foo)
l = cls()
l += [1, 2]
self.assertEqual(len(objs), 2)
self.assertEqual(type(l[0]), Foo)
self.assertEqual(type(l[1]), Foo)
# Adding a TypedList to a TypedList shouldn't trigger coercion again
l2 = cls()
l2 += l
self.assertEqual(len(objs), 2)
self.assertEqual(type(l2[0]), Foo)
self.assertEqual(type(l2[1]), Foo)
# Adding a TypedList to a TypedList shouldn't even trigger the code
# that does coercion at all.
l2 = cls()
list.__setslice__(l, 0, -1, [1, 2])
l2 += l
self.assertEqual(len(objs), 2)
self.assertEqual(type(l2[0]), int)
self.assertEqual(type(l2[1]), int)
def test_memoized(self):
cls = TypedList(int)
cls2 = TypedList(str)
self.assertEqual(TypedList(int), cls)
self.assertNotEqual(cls, cls2)
class TypedTestStrictOrderingOnAppendList(unittest.TestCase):
def test_init(self):
class Unicode(unicode):
def __init__(self, other):
if not isinstance(other, unicode):
raise ValueError()
super(Unicode, self).__init__(other)
cls = TypedList(Unicode, StrictOrderingOnAppendList)
l = cls()
self.assertEqual(len(l), 0)
l = cls(['a', 'b', 'c'])
self.assertEqual(len(l), 3)
with self.assertRaises(UnsortedError):
cls(['c', 'b', 'a'])
with self.assertRaises(ValueError):
cls(['a', 'b', 3])
self.assertEqual(len(l), 3)
if __name__ == '__main__':
main()

View File

@ -233,24 +233,24 @@ def resolve_target_to_make(topobjdir, target):
reldir = os.path.dirname(reldir)
class List(list):
"""A list specialized for moz.build environments.
class ListMixin(object):
def __init__(self, iterable=[]):
if not isinstance(iterable, list):
raise ValueError('List can only be created from other list instances.')
return super(ListMixin, self).__init__(iterable)
We overload the assignment and append operations to require that the
appended thing is a list. This avoids bad surprises coming from appending
a string to a list, which would just add each letter of the string.
"""
def extend(self, l):
if not isinstance(l, list):
raise ValueError('List can only be extended with other list instances.')
return list.extend(self, l)
return super(ListMixin, self).extend(l)
def __setslice__(self, i, j, sequence):
if not isinstance(sequence, list):
raise ValueError('List can only be sliced with other list instances.')
return list.__setslice__(self, i, j, sequence)
return super(ListMixin, self).__setslice__(i, j, sequence)
def __add__(self, other):
# Allow None is a special case because it makes undefined variable
@ -259,16 +259,25 @@ class List(list):
if not isinstance(other, list):
raise ValueError('Only lists can be appended to lists.')
return List(list.__add__(self, other))
new_list = self.__class__(self)
new_list.extend(other)
return new_list
def __iadd__(self, other):
other = [] if other is None else other
if not isinstance(other, list):
raise ValueError('Only lists can be appended to lists.')
list.__iadd__(self, other)
return super(ListMixin, self).__iadd__(other)
return self
class List(ListMixin, list):
"""A list specialized for moz.build environments.
We overload the assignment and append operations to require that the
appended thing is a list. This avoids bad surprises coming from appending
a string to a list, which would just add each letter of the string.
"""
class UnsortedError(Exception):
@ -297,12 +306,7 @@ class UnsortedError(Exception):
return s.getvalue()
class StrictOrderingOnAppendList(list):
"""A list specialized for moz.build environments.
We overload the assignment and append operations to require that incoming
elements be ordered. This enforces cleaner style in moz.build files.
"""
class StrictOrderingOnAppendListMixin(object):
@staticmethod
def ensure_sorted(l):
if isinstance(l, StrictOrderingOnAppendList):
@ -314,46 +318,39 @@ class StrictOrderingOnAppendList(list):
raise UnsortedError(srtd, l)
def __init__(self, iterable=[]):
StrictOrderingOnAppendList.ensure_sorted(iterable)
StrictOrderingOnAppendListMixin.ensure_sorted(iterable)
list.__init__(self, iterable)
super(StrictOrderingOnAppendListMixin, self).__init__(iterable)
def extend(self, l):
if not isinstance(l, list):
raise ValueError('List can only be extended with other list instances.')
StrictOrderingOnAppendListMixin.ensure_sorted(l)
StrictOrderingOnAppendList.ensure_sorted(l)
return list.extend(self, l)
return super(StrictOrderingOnAppendListMixin, self).extend(l)
def __setslice__(self, i, j, sequence):
if not isinstance(sequence, list):
raise ValueError('List can only be sliced with other list instances.')
StrictOrderingOnAppendListMixin.ensure_sorted(sequence)
StrictOrderingOnAppendList.ensure_sorted(sequence)
return list.__setslice__(self, i, j, sequence)
return super(StrictOrderingOnAppendListMixin, self).__setslice__(i, j,
sequence)
def __add__(self, other):
if not isinstance(other, list):
raise ValueError('Only lists can be appended to lists.')
StrictOrderingOnAppendListMixin.ensure_sorted(other)
new_list = StrictOrderingOnAppendList()
# Can't extend with self because it may already be the result of
# several extensions and not be ordered.
list.extend(new_list, self)
new_list.extend(other)
return new_list
return super(StrictOrderingOnAppendListMixin, self).__add__(other)
def __iadd__(self, other):
if not isinstance(other, list):
raise ValueError('Only lists can be appended to lists.')
StrictOrderingOnAppendListMixin.ensure_sorted(other)
StrictOrderingOnAppendList.ensure_sorted(other)
return super(StrictOrderingOnAppendListMixin, self).__iadd__(other)
list.__iadd__(self, other)
return self
class StrictOrderingOnAppendList(ListMixin, StrictOrderingOnAppendListMixin,
list):
"""A list specialized for moz.build environments.
We overload the assignment and append operations to require that incoming
elements be ordered. This enforces cleaner style in moz.build files.
"""
class MozbuildDeletionError(Exception):
@ -817,3 +814,64 @@ class memoized_property(object):
if not hasattr(instance, name):
setattr(instance, name, self.func(instance))
return getattr(instance, name)
class TypedListMixin(object):
'''Mixin for a list with type coercion. See TypedList.'''
def _ensure_type(self, l):
if isinstance(l, self.__class__):
return l
def normalize(e):
if not isinstance(e, self.TYPE):
e = self.TYPE(e)
return e
return [normalize(e) for e in l]
def __init__(self, iterable=[]):
iterable = self._ensure_type(iterable)
super(TypedListMixin, self).__init__(iterable)
def extend(self, l):
l = self._ensure_type(l)
return super(TypedListMixin, self).extend(l)
def __setslice__(self, i, j, sequence):
sequence = self._ensure_type(sequence)
return super(TypedListMixin, self).__setslice__(i, j,
sequence)
def __add__(self, other):
other = self._ensure_type(other)
return super(TypedListMixin, self).__add__(other)
def __iadd__(self, other):
other = self._ensure_type(other)
return super(TypedListMixin, self).__iadd__(other)
def append(self, other):
self += [other]
@memoize
def TypedList(type, base_class=List):
'''A list with type coercion.
The given ``type`` is what list elements are being coerced to. It may do
strict validation, throwing ValueError exceptions.
A ``base_class`` type can be given for more specific uses than a List. For
example, a Typed StrictOrderingOnAppendList can be created with:
TypedList(unicode, StrictOrderingOnAppendList)
'''
class _TypedList(TypedListMixin, base_class):
TYPE = type
return _TypedList