mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Bug 1062221 - Add a TypedList type and refactor mozbuild.util lists. r=gps
This commit is contained in:
parent
276d86f006
commit
61655fba8e
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user