move smart_list into sub-package/multiple filestags/v0.6
@@ -1,6 +1,7 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# | # | ||||
# Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | # Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | ||||
# Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
# | # | ||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
# of this software and associated documentation files (the "Software"), to deal | # of this software and associated documentation files (the "Software"), to deal | ||||
@@ -20,201 +21,10 @@ | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
# SOFTWARE. | # SOFTWARE. | ||||
""" | |||||
This module contains the :class:`.SmartList` type, as well as its | |||||
:class:`._ListProxy` child, which together implement a list whose sublists | |||||
reflect changes made to the main list, and vice-versa. | |||||
""" | |||||
from __future__ import unicode_literals | |||||
from sys import maxsize | |||||
from weakref import ref | |||||
from .compat import py3k | |||||
__all__ = ["SmartList"] | |||||
def inheritdoc(method): | |||||
"""Set __doc__ of *method* to __doc__ of *method* in its parent class. | |||||
Since this is used on :class:`.SmartList`, the "parent class" used is | |||||
``list``. This function can be used as a decorator. | |||||
""" | |||||
method.__doc__ = getattr(list, method.__name__).__doc__ | |||||
return method | |||||
class _SliceNormalizerMixIn(object): | |||||
"""MixIn that provides a private method to normalize slices.""" | |||||
def _normalize_slice(self, key, clamp=False): | |||||
"""Return a slice equivalent to the input *key*, standardized.""" | |||||
if key.start is None: | |||||
start = 0 | |||||
else: | |||||
start = (len(self) + key.start) if key.start < 0 else key.start | |||||
if key.stop is None or key.stop == maxsize: | |||||
stop = len(self) if clamp else None | |||||
else: | |||||
stop = (len(self) + key.stop) if key.stop < 0 else key.stop | |||||
return slice(start, stop, key.step or 1) | |||||
class SmartList(_SliceNormalizerMixIn, list): | |||||
"""Implements the ``list`` interface with special handling of sublists. | |||||
When a sublist is created (by ``list[i:j]``), any changes made to this | |||||
list (such as the addition, removal, or replacement of elements) will be | |||||
reflected in the sublist, or vice-versa, to the greatest degree possible. | |||||
This is implemented by having sublists - instances of the | |||||
:class:`._ListProxy` type - dynamically determine their elements by storing | |||||
their slice info and retrieving that slice from the parent. Methods that | |||||
change the size of the list also change the slice info. For example:: | |||||
>>> parent = SmartList([0, 1, 2, 3]) | |||||
>>> parent | |||||
[0, 1, 2, 3] | |||||
>>> child = parent[2:] | |||||
>>> child | |||||
[2, 3] | |||||
>>> child.append(4) | |||||
>>> child | |||||
[2, 3, 4] | |||||
>>> parent | |||||
[0, 1, 2, 3, 4] | |||||
""" | |||||
def __init__(self, iterable=None): | |||||
if iterable: | |||||
super(SmartList, self).__init__(iterable) | |||||
else: | |||||
super(SmartList, self).__init__() | |||||
self._children = {} | |||||
def __getitem__(self, key): | |||||
if not isinstance(key, slice): | |||||
return super(SmartList, self).__getitem__(key) | |||||
key = self._normalize_slice(key, clamp=False) | |||||
sliceinfo = [key.start, key.stop, key.step] | |||||
child = _ListProxy(self, sliceinfo) | |||||
child_ref = ref(child, self._delete_child) | |||||
self._children[id(child_ref)] = (child_ref, sliceinfo) | |||||
return child | |||||
def __setitem__(self, key, item): | |||||
if not isinstance(key, slice): | |||||
return super(SmartList, self).__setitem__(key, item) | |||||
item = list(item) | |||||
super(SmartList, self).__setitem__(key, item) | |||||
key = self._normalize_slice(key, clamp=True) | |||||
diff = len(item) + (key.start - key.stop) // key.step | |||||
if not diff: | |||||
return | |||||
values = self._children.values if py3k else self._children.itervalues | |||||
for child, (start, stop, step) in values(): | |||||
if start > key.stop: | |||||
self._children[id(child)][1][0] += diff | |||||
if stop is not None and stop >= key.stop: | |||||
self._children[id(child)][1][1] += diff | |||||
def __delitem__(self, key): | |||||
super(SmartList, self).__delitem__(key) | |||||
if isinstance(key, slice): | |||||
key = self._normalize_slice(key, clamp=True) | |||||
else: | |||||
key = slice(key, key + 1, 1) | |||||
diff = (key.stop - key.start) // key.step | |||||
values = self._children.values if py3k else self._children.itervalues | |||||
for child, (start, stop, step) in values(): | |||||
if start > key.start: | |||||
self._children[id(child)][1][0] -= diff | |||||
if stop is not None and stop >= key.stop: | |||||
self._children[id(child)][1][1] -= diff | |||||
if not py3k: | |||||
def __getslice__(self, start, stop): | |||||
return self.__getitem__(slice(start, stop)) | |||||
def __setslice__(self, start, stop, iterable): | |||||
self.__setitem__(slice(start, stop), iterable) | |||||
def __delslice__(self, start, stop): | |||||
self.__delitem__(slice(start, stop)) | |||||
def __add__(self, other): | |||||
return SmartList(list(self) + other) | |||||
def __radd__(self, other): | |||||
return SmartList(other + list(self)) | |||||
def __iadd__(self, other): | |||||
self.extend(other) | |||||
return self | |||||
def _delete_child(self, child_ref): | |||||
"""Remove a child reference that is about to be garbage-collected.""" | |||||
del self._children[id(child_ref)] | |||||
def _detach_children(self): | |||||
"""Remove all children and give them independent parent copies.""" | |||||
children = [val[0] for val in self._children.values()] | |||||
for child in children: | |||||
child()._parent = list(self) | |||||
self._children.clear() | |||||
@inheritdoc | |||||
def append(self, item): | |||||
head = len(self) | |||||
self[head:head] = [item] | |||||
@inheritdoc | |||||
def extend(self, item): | |||||
head = len(self) | |||||
self[head:head] = item | |||||
@inheritdoc | |||||
def insert(self, index, item): | |||||
self[index:index] = [item] | |||||
@inheritdoc | |||||
def pop(self, index=None): | |||||
if index is None: | |||||
index = len(self) - 1 | |||||
item = self[index] | |||||
del self[index] | |||||
return item | |||||
@inheritdoc | |||||
def remove(self, item): | |||||
del self[self.index(item)] | |||||
@inheritdoc | |||||
def reverse(self): | |||||
self._detach_children() | |||||
super(SmartList, self).reverse() | |||||
if py3k: | |||||
@inheritdoc | |||||
def sort(self, key=None, reverse=None): | |||||
self._detach_children() | |||||
kwargs = {} | |||||
if key is not None: | |||||
kwargs["key"] = key | |||||
if reverse is not None: | |||||
kwargs["reverse"] = reverse | |||||
super(SmartList, self).sort(**kwargs) | |||||
else: | |||||
@inheritdoc | |||||
def sort(self, cmp=None, key=None, reverse=None): | |||||
self._detach_children() | |||||
kwargs = {} | |||||
if cmp is not None: | |||||
kwargs["cmp"] = cmp | |||||
if key is not None: | |||||
kwargs["key"] = key | |||||
if reverse is not None: | |||||
kwargs["reverse"] = reverse | |||||
super(SmartList, self).sort(**kwargs) | |||||
# SmartList has to be a full import in order to avoid cyclical import errors | |||||
import mwparserfromhell.smart_list.SmartList | |||||
from .utils import _SliceNormalizerMixIn, inheritdoc | |||||
from ..compat import py3k | |||||
class _ListProxy(_SliceNormalizerMixIn, list): | class _ListProxy(_SliceNormalizerMixIn, list): | ||||
@@ -339,20 +149,20 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||||
self.__delitem__(slice(start, stop)) | self.__delitem__(slice(start, stop)) | ||||
def __add__(self, other): | def __add__(self, other): | ||||
return SmartList(list(self) + other) | |||||
return mwparserfromhell.smart_list.SmartList(list(self) + other) | |||||
def __radd__(self, other): | def __radd__(self, other): | ||||
return SmartList(other + list(self)) | |||||
return mwparserfromhell.smart_list.SmartList(other + list(self)) | |||||
def __iadd__(self, other): | def __iadd__(self, other): | ||||
self.extend(other) | self.extend(other) | ||||
return self | return self | ||||
def __mul__(self, other): | def __mul__(self, other): | ||||
return SmartList(list(self) * other) | |||||
return mwparserfromhell.smart_list.SmartList(list(self) * other) | |||||
def __rmul__(self, other): | def __rmul__(self, other): | ||||
return SmartList(other * list(self)) | |||||
return mwparserfromhell.smart_list.SmartList(other * list(self)) | |||||
def __imul__(self, other): | def __imul__(self, other): | ||||
self.extend(list(self) * (other - 1)) | self.extend(list(self) * (other - 1)) | ||||
@@ -451,6 +261,3 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||||
kwargs["reverse"] = reverse | kwargs["reverse"] = reverse | ||||
item.sort(**kwargs) | item.sort(**kwargs) | ||||
self._parent[self._start:self._stop:self._step] = item | self._parent[self._start:self._stop:self._step] = item | ||||
del inheritdoc |
@@ -0,0 +1,185 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
# | |||||
# 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. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from _weakref import ref | |||||
from .ListProxy import _ListProxy | |||||
from .utils import _SliceNormalizerMixIn, inheritdoc | |||||
from ..compat import py3k | |||||
class SmartList(_SliceNormalizerMixIn, list): | |||||
"""Implements the ``list`` interface with special handling of sublists. | |||||
When a sublist is created (by ``list[i:j]``), any changes made to this | |||||
list (such as the addition, removal, or replacement of elements) will be | |||||
reflected in the sublist, or vice-versa, to the greatest degree possible. | |||||
This is implemented by having sublists - instances of the | |||||
:class:`._ListProxy` type - dynamically determine their elements by storing | |||||
their slice info and retrieving that slice from the parent. Methods that | |||||
change the size of the list also change the slice info. For example:: | |||||
>>> parent = SmartList([0, 1, 2, 3]) | |||||
>>> parent | |||||
[0, 1, 2, 3] | |||||
>>> child = parent[2:] | |||||
>>> child | |||||
[2, 3] | |||||
>>> child.append(4) | |||||
>>> child | |||||
[2, 3, 4] | |||||
>>> parent | |||||
[0, 1, 2, 3, 4] | |||||
""" | |||||
def __init__(self, iterable=None): | |||||
if iterable: | |||||
super(SmartList, self).__init__(iterable) | |||||
else: | |||||
super(SmartList, self).__init__() | |||||
self._children = {} | |||||
def __getitem__(self, key): | |||||
if not isinstance(key, slice): | |||||
return super(SmartList, self).__getitem__(key) | |||||
key = self._normalize_slice(key, clamp=False) | |||||
sliceinfo = [key.start, key.stop, key.step] | |||||
child = _ListProxy(self, sliceinfo) | |||||
child_ref = ref(child, self._delete_child) | |||||
self._children[id(child_ref)] = (child_ref, sliceinfo) | |||||
return child | |||||
def __setitem__(self, key, item): | |||||
if not isinstance(key, slice): | |||||
return super(SmartList, self).__setitem__(key, item) | |||||
item = list(item) | |||||
super(SmartList, self).__setitem__(key, item) | |||||
key = self._normalize_slice(key, clamp=True) | |||||
diff = len(item) + (key.start - key.stop) // key.step | |||||
if not diff: | |||||
return | |||||
values = self._children.values if py3k else self._children.itervalues | |||||
for child, (start, stop, step) in values(): | |||||
if start > key.stop: | |||||
self._children[id(child)][1][0] += diff | |||||
if stop is not None and stop >= key.stop: | |||||
self._children[id(child)][1][1] += diff | |||||
def __delitem__(self, key): | |||||
super(SmartList, self).__delitem__(key) | |||||
if isinstance(key, slice): | |||||
key = self._normalize_slice(key, clamp=True) | |||||
else: | |||||
key = slice(key, key + 1, 1) | |||||
diff = (key.stop - key.start) // key.step | |||||
values = self._children.values if py3k else self._children.itervalues | |||||
for child, (start, stop, step) in values(): | |||||
if start > key.start: | |||||
self._children[id(child)][1][0] -= diff | |||||
if stop is not None and stop >= key.stop: | |||||
self._children[id(child)][1][1] -= diff | |||||
if not py3k: | |||||
def __getslice__(self, start, stop): | |||||
return self.__getitem__(slice(start, stop)) | |||||
def __setslice__(self, start, stop, iterable): | |||||
self.__setitem__(slice(start, stop), iterable) | |||||
def __delslice__(self, start, stop): | |||||
self.__delitem__(slice(start, stop)) | |||||
def __add__(self, other): | |||||
return SmartList(list(self) + other) | |||||
def __radd__(self, other): | |||||
return SmartList(other + list(self)) | |||||
def __iadd__(self, other): | |||||
self.extend(other) | |||||
return self | |||||
def _delete_child(self, child_ref): | |||||
"""Remove a child reference that is about to be garbage-collected.""" | |||||
del self._children[id(child_ref)] | |||||
def _detach_children(self): | |||||
"""Remove all children and give them independent parent copies.""" | |||||
children = [val[0] for val in self._children.values()] | |||||
for child in children: | |||||
child()._parent = list(self) | |||||
self._children.clear() | |||||
@inheritdoc | |||||
def append(self, item): | |||||
head = len(self) | |||||
self[head:head] = [item] | |||||
@inheritdoc | |||||
def extend(self, item): | |||||
head = len(self) | |||||
self[head:head] = item | |||||
@inheritdoc | |||||
def insert(self, index, item): | |||||
self[index:index] = [item] | |||||
@inheritdoc | |||||
def pop(self, index=None): | |||||
if index is None: | |||||
index = len(self) - 1 | |||||
item = self[index] | |||||
del self[index] | |||||
return item | |||||
@inheritdoc | |||||
def remove(self, item): | |||||
del self[self.index(item)] | |||||
@inheritdoc | |||||
def reverse(self): | |||||
self._detach_children() | |||||
super(SmartList, self).reverse() | |||||
if py3k: | |||||
@inheritdoc | |||||
def sort(self, key=None, reverse=None): | |||||
self._detach_children() | |||||
kwargs = {} | |||||
if key is not None: | |||||
kwargs["key"] = key | |||||
if reverse is not None: | |||||
kwargs["reverse"] = reverse | |||||
super(SmartList, self).sort(**kwargs) | |||||
else: | |||||
@inheritdoc | |||||
def sort(self, cmp=None, key=None, reverse=None): | |||||
self._detach_children() | |||||
kwargs = {} | |||||
if cmp is not None: | |||||
kwargs["cmp"] = cmp | |||||
if key is not None: | |||||
kwargs["key"] = key | |||||
if reverse is not None: | |||||
kwargs["reverse"] = reverse | |||||
super(SmartList, self).sort(**kwargs) |
@@ -0,0 +1,30 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
# | |||||
# 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. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
""" | |||||
This module contains the :class:`.SmartList` type, as well as its | |||||
:class:`._ListProxy` child, which together implement a list whose sublists | |||||
reflect changes made to the main list, and vice-versa. | |||||
""" | |||||
from .SmartList import SmartList |
@@ -0,0 +1,54 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2012-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2019-2020 Yuri Astrakhan <YuriAstrakhan@gmail.com> | |||||
# | |||||
# 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. | |||||
# | |||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
# SOFTWARE. | |||||
from __future__ import unicode_literals | |||||
from sys import maxsize | |||||
__all__ = [] | |||||
def inheritdoc(method): | |||||
"""Set __doc__ of *method* to __doc__ of *method* in its parent class. | |||||
Since this is used on :class:`.SmartList`, the "parent class" used is | |||||
``list``. This function can be used as a decorator. | |||||
""" | |||||
method.__doc__ = getattr(list, method.__name__).__doc__ | |||||
return method | |||||
class _SliceNormalizerMixIn(object): | |||||
"""MixIn that provides a private method to normalize slices.""" | |||||
def _normalize_slice(self, key, clamp=False): | |||||
"""Return a slice equivalent to the input *key*, standardized.""" | |||||
if key.start is None: | |||||
start = 0 | |||||
else: | |||||
start = (len(self) + key.start) if key.start < 0 else key.start | |||||
if key.stop is None or key.stop == maxsize: | |||||
stop = len(self) if clamp else None | |||||
else: | |||||
stop = (len(self) + key.stop) if key.stop < 0 else key.stop | |||||
return slice(start, stop, key.step or 1) |
@@ -21,13 +21,14 @@ | |||||
# SOFTWARE. | # SOFTWARE. | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
from itertools import chain | |||||
import re | import re | ||||
from itertools import chain | |||||
from .compat import bytes, py3k, range, str | from .compat import bytes, py3k, range, str | ||||
from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | ||||
Node, Tag, Template, Text, Wikilink) | Node, Tag, Template, Text, Wikilink) | ||||
from .smart_list import _ListProxy | |||||
from .smart_list.ListProxy import _ListProxy | |||||
from .string_mixin import StringMixIn | from .string_mixin import StringMixIn | ||||
from .utils import parse_anything | from .utils import parse_anything | ||||
@@ -21,10 +21,13 @@ | |||||
# SOFTWARE. | # SOFTWARE. | ||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import unittest | import unittest | ||||
from mwparserfromhell.compat import py3k, range | from mwparserfromhell.compat import py3k, range | ||||
from mwparserfromhell.smart_list import SmartList, _ListProxy | |||||
from mwparserfromhell.smart_list import SmartList | |||||
from mwparserfromhell.smart_list.ListProxy import _ListProxy | |||||
class TestSmartList(unittest.TestCase): | class TestSmartList(unittest.TestCase): | ||||
"""Test cases for the SmartList class and its child, _ListProxy.""" | """Test cases for the SmartList class and its child, _ListProxy.""" | ||||