move smart_list into sub-package/multiple filestags/v0.6
@@ -1,6 +1,7 @@ | |||
# -*- 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 | |||
@@ -20,201 +21,10 @@ | |||
# 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 __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): | |||
@@ -339,20 +149,20 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||
self.__delitem__(slice(start, stop)) | |||
def __add__(self, other): | |||
return SmartList(list(self) + other) | |||
return mwparserfromhell.smart_list.SmartList(list(self) + other) | |||
def __radd__(self, other): | |||
return SmartList(other + list(self)) | |||
return mwparserfromhell.smart_list.SmartList(other + list(self)) | |||
def __iadd__(self, other): | |||
self.extend(other) | |||
return self | |||
def __mul__(self, other): | |||
return SmartList(list(self) * other) | |||
return mwparserfromhell.smart_list.SmartList(list(self) * other) | |||
def __rmul__(self, other): | |||
return SmartList(other * list(self)) | |||
return mwparserfromhell.smart_list.SmartList(other * list(self)) | |||
def __imul__(self, other): | |||
self.extend(list(self) * (other - 1)) | |||
@@ -451,6 +261,3 @@ class _ListProxy(_SliceNormalizerMixIn, list): | |||
kwargs["reverse"] = reverse | |||
item.sort(**kwargs) | |||
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. | |||
from __future__ import unicode_literals | |||
from itertools import chain | |||
import re | |||
from itertools import chain | |||
from .compat import bytes, py3k, range, str | |||
from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | |||
Node, Tag, Template, Text, Wikilink) | |||
from .smart_list import _ListProxy | |||
from .smart_list.ListProxy import _ListProxy | |||
from .string_mixin import StringMixIn | |||
from .utils import parse_anything | |||
@@ -21,10 +21,13 @@ | |||
# SOFTWARE. | |||
from __future__ import unicode_literals | |||
import unittest | |||
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): | |||
"""Test cases for the SmartList class and its child, _ListProxy.""" | |||