This is a first draft of the smart list. It passes all of the existing smartlist tests (except for full-list reversal, whose behavior has changed), but the rest of the code has not yet been updated.pull/235/head
@@ -27,7 +27,7 @@ from ..compat import str | |||
from ..nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, Tag, | |||
Template, Text, Wikilink) | |||
from ..nodes.extras import Attribute, Parameter | |||
from ..smart_list import SmartList | |||
from ..smart_list import smart_list | |||
from ..wikicode import Wikicode | |||
__all__ = ["Builder"] | |||
@@ -67,7 +67,7 @@ class Builder(object): | |||
The raw node list is wrapped in a :class:`.SmartList` and then in a | |||
:class:`.Wikicode` object. | |||
""" | |||
return Wikicode(SmartList(self._stacks.pop())) | |||
return Wikicode(smart_list(self._stacks.pop())) | |||
def _write(self, item): | |||
"""Append a node to the current node list.""" | |||
@@ -92,7 +92,7 @@ class Builder(object): | |||
self._tokens.append(token) | |||
value = self._pop() | |||
if key is None: | |||
key = Wikicode(SmartList([Text(str(default))])) | |||
key = Wikicode(smart_list([Text(str(default))])) | |||
return Parameter(key, value, showkey) | |||
else: | |||
self._write(self._handle_token(token)) | |||
@@ -1,263 +0,0 @@ | |||
# -*- 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. | |||
# 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): | |||
"""Implement the ``list`` interface by getting elements from a parent. | |||
This is created by a :class:`.SmartList` object when slicing. It does not | |||
actually store the list at any time; instead, whenever the list is needed, | |||
it builds it dynamically using the :meth:`_render` method. | |||
""" | |||
def __init__(self, parent, sliceinfo): | |||
super(_ListProxy, self).__init__() | |||
self._parent = parent | |||
self._sliceinfo = sliceinfo | |||
def __repr__(self): | |||
return repr(self._render()) | |||
def __lt__(self, other): | |||
if isinstance(other, _ListProxy): | |||
return self._render() < list(other) | |||
return self._render() < other | |||
def __le__(self, other): | |||
if isinstance(other, _ListProxy): | |||
return self._render() <= list(other) | |||
return self._render() <= other | |||
def __eq__(self, other): | |||
if isinstance(other, _ListProxy): | |||
return self._render() == list(other) | |||
return self._render() == other | |||
def __ne__(self, other): | |||
if isinstance(other, _ListProxy): | |||
return self._render() != list(other) | |||
return self._render() != other | |||
def __gt__(self, other): | |||
if isinstance(other, _ListProxy): | |||
return self._render() > list(other) | |||
return self._render() > other | |||
def __ge__(self, other): | |||
if isinstance(other, _ListProxy): | |||
return self._render() >= list(other) | |||
return self._render() >= other | |||
if py3k: | |||
def __bool__(self): | |||
return bool(self._render()) | |||
else: | |||
def __nonzero__(self): | |||
return bool(self._render()) | |||
def __len__(self): | |||
return max((self._stop - self._start) // self._step, 0) | |||
def __getitem__(self, key): | |||
if isinstance(key, slice): | |||
key = self._normalize_slice(key, clamp=True) | |||
keystart = min(self._start + key.start, self._stop) | |||
keystop = min(self._start + key.stop, self._stop) | |||
adjusted = slice(keystart, keystop, key.step) | |||
return self._parent[adjusted] | |||
else: | |||
return self._render()[key] | |||
def __setitem__(self, key, item): | |||
if isinstance(key, slice): | |||
key = self._normalize_slice(key, clamp=True) | |||
keystart = min(self._start + key.start, self._stop) | |||
keystop = min(self._start + key.stop, self._stop) | |||
adjusted = slice(keystart, keystop, key.step) | |||
self._parent[adjusted] = item | |||
else: | |||
length = len(self) | |||
if key < 0: | |||
key = length + key | |||
if key < 0 or key >= length: | |||
raise IndexError("list assignment index out of range") | |||
self._parent[self._start + key] = item | |||
def __delitem__(self, key): | |||
if isinstance(key, slice): | |||
key = self._normalize_slice(key, clamp=True) | |||
keystart = min(self._start + key.start, self._stop) | |||
keystop = min(self._start + key.stop, self._stop) | |||
adjusted = slice(keystart, keystop, key.step) | |||
del self._parent[adjusted] | |||
else: | |||
length = len(self) | |||
if key < 0: | |||
key = length + key | |||
if key < 0 or key >= length: | |||
raise IndexError("list assignment index out of range") | |||
del self._parent[self._start + key] | |||
def __iter__(self): | |||
i = self._start | |||
while i < self._stop: | |||
yield self._parent[i] | |||
i += self._step | |||
def __reversed__(self): | |||
i = self._stop - 1 | |||
while i >= self._start: | |||
yield self._parent[i] | |||
i -= self._step | |||
def __contains__(self, item): | |||
return item in self._render() | |||
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 mwparserfromhell.smart_list.SmartList(list(self) + other) | |||
def __radd__(self, other): | |||
return mwparserfromhell.smart_list.SmartList(other + list(self)) | |||
def __iadd__(self, other): | |||
self.extend(other) | |||
return self | |||
def __mul__(self, other): | |||
return mwparserfromhell.smart_list.SmartList(list(self) * other) | |||
def __rmul__(self, other): | |||
return mwparserfromhell.smart_list.SmartList(other * list(self)) | |||
def __imul__(self, other): | |||
self.extend(list(self) * (other - 1)) | |||
return self | |||
@property | |||
def _start(self): | |||
"""The starting index of this list, inclusive.""" | |||
return self._sliceinfo[0] | |||
@property | |||
def _stop(self): | |||
"""The ending index of this list, exclusive.""" | |||
if self._sliceinfo[1] is None: | |||
return len(self._parent) | |||
return self._sliceinfo[1] | |||
@property | |||
def _step(self): | |||
"""The number to increase the index by between items.""" | |||
return self._sliceinfo[2] | |||
def _render(self): | |||
"""Return the actual list from the stored start/stop/step.""" | |||
return list(self._parent)[self._start:self._stop:self._step] | |||
@inheritdoc | |||
def append(self, item): | |||
self._parent.insert(self._stop, item) | |||
@inheritdoc | |||
def count(self, item): | |||
return self._render().count(item) | |||
@inheritdoc | |||
def index(self, item, start=None, stop=None): | |||
if start is not None: | |||
if stop is not None: | |||
return self._render().index(item, start, stop) | |||
return self._render().index(item, start) | |||
return self._render().index(item) | |||
@inheritdoc | |||
def extend(self, item): | |||
self._parent[self._stop:self._stop] = item | |||
@inheritdoc | |||
def insert(self, index, item): | |||
if index < 0: | |||
index = len(self) + index | |||
self._parent.insert(self._start + index, item) | |||
@inheritdoc | |||
def pop(self, index=None): | |||
length = len(self) | |||
if index is None: | |||
index = length - 1 | |||
elif index < 0: | |||
index = length + index | |||
if index < 0 or index >= length: | |||
raise IndexError("pop index out of range") | |||
return self._parent.pop(self._start + index) | |||
@inheritdoc | |||
def remove(self, item): | |||
index = self.index(item) | |||
del self._parent[self._start + index] | |||
@inheritdoc | |||
def reverse(self): | |||
item = self._render() | |||
item.reverse() | |||
self._parent[self._start:self._stop:self._step] = item | |||
if py3k: | |||
@inheritdoc | |||
def sort(self, key=None, reverse=None): | |||
item = self._render() | |||
kwargs = {} | |||
if key is not None: | |||
kwargs["key"] = key | |||
if reverse is not None: | |||
kwargs["reverse"] = reverse | |||
item.sort(**kwargs) | |||
self._parent[self._start:self._stop:self._step] = item | |||
else: | |||
@inheritdoc | |||
def sort(self, cmp=None, key=None, reverse=None): | |||
item = self._render() | |||
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 | |||
item.sort(**kwargs) | |||
self._parent[self._start:self._stop:self._step] = item |
@@ -1,185 +0,0 @@ | |||
# -*- 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) |
@@ -27,4 +27,4 @@ This module contains the :class:`.SmartList` type, as well as its | |||
reflect changes made to the main list, and vice-versa. | |||
""" | |||
from .SmartList import SmartList | |||
from .smart_list import smart_list |
@@ -0,0 +1,409 @@ | |||
# -*- 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 typing import Tuple | |||
from .utils import inheritdoc | |||
from ..compat import py3k | |||
class ListStore: | |||
def __init__(self, iterator=None, attach=False): | |||
if attach: | |||
self.data = iterator | |||
else: | |||
self.data = [] if iterator is None else list(iterator) | |||
self.slices = {} | |||
def attach_slice(self, slc): | |||
slc_ref = ref(slc, self.detach_slice_ref) | |||
self.slices[id(slc_ref)] = slc_ref | |||
def detach_slice_ref(self, slice_ref): | |||
"""Remove a child reference that is about to be garbage-collected.""" | |||
del self.slices[id(slice_ref)] | |||
def detach_slice(self, slc_to_delete): | |||
""" | |||
:type slc_to_delete: SmartSlice | |||
""" | |||
for sid, slc_ref in self.slices.items(): | |||
# slc: Slice | |||
slc = slc_ref() | |||
if slc is not None and slc is slc_to_delete: | |||
del self.slices[sid] | |||
break | |||
class SmartSlice: | |||
def __init__(self, store, start, stop, step): | |||
assert step is not None and step != 0 | |||
assert start is None or stop is None or start <= stop | |||
# self._store: Store | |||
self._store = store | |||
# self._start: Union[int, None] | |||
self._start = start | |||
# self._stop: Union[int, None] | |||
self._stop = stop | |||
# self._step: int | |||
self._step = step | |||
self._store.attach_slice(self) | |||
def _update_indexes(self, start, stop, shift): | |||
if shift: | |||
for slc_ref in self._store.slices.values(): | |||
# slc: SmartSlice | |||
slc = slc_ref() | |||
if slc is not None: | |||
if slc._start is not None and stop < slc._start: | |||
slc._start += shift | |||
if slc._stop is not None and start <= slc._stop: | |||
slc._stop += shift | |||
def _render(self): | |||
"""Return the actual list from the stored start/stop/step.""" | |||
if self._start is None: | |||
if self._stop is None: | |||
return self._store.data[::self._step] | |||
else: | |||
return self._store.data[:self._stop:self._step] | |||
elif self._stop is None: | |||
return self._store.data[self._start::self._step] | |||
else: | |||
return self._store.data[self._start:self._stop:self._step] | |||
def _adjust(self, start, stop, step, materialize=False): | |||
""" | |||
:rtype: Tuple[int, int, int, int, int] | |||
""" | |||
int_start = 0 if self._start is None else self._start | |||
int_stop = len(self._store.data) if self._stop is None else self._stop | |||
if self._step > 0: | |||
if start is None: | |||
_start = self._start | |||
else: | |||
_start = min(int_stop, max(int_start, ( | |||
int_start if start >= 0 else int_stop) + start)) | |||
if stop is None: | |||
_stop = self._stop | |||
else: | |||
_stop = min(int_stop, max(int_start, ( | |||
int_start if stop >= 0 else int_stop) + stop)) | |||
_step = self._step if step is None else (self._step * step) | |||
else: | |||
raise ValueError("Not implemented") | |||
# _start = stop if self._start is None else ( | |||
# self._stop if stop is None else (self._stop + stop)) | |||
# _stop = stop if self._stop is None else ( | |||
# self._stop if stop is None else (self._stop + stop)) | |||
# _step = self._step if step is None else (self._step * step) | |||
if materialize: | |||
if _start is None: | |||
_start = int_start | |||
if _stop is None: | |||
_stop = int_stop | |||
rng_start = _start if self._start is None else int_start | |||
rng_stop = _stop if self._stop is None else int_stop | |||
return _start, _stop, _step, rng_start, rng_stop | |||
def _adjust_pos(self, pos, validate): | |||
""" | |||
:type pos: int | |||
:type validate: bool | |||
:rtype: int | |||
""" | |||
assert isinstance(pos, int) | |||
int_start = 0 if self._start is None else self._start | |||
int_stop = len(self._store.data) if self._stop is None else self._stop | |||
if self._step > 0: | |||
_pos = (int_start if pos >= 0 else int_stop) + pos | |||
else: | |||
raise ValueError("Not implemented") | |||
if validate and not (int_start <= _pos < int_stop): | |||
raise IndexError('list index out of range') | |||
return _pos | |||
# 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() | |||
def __getitem__(self, key): | |||
if isinstance(key, slice): | |||
start, stop, step, _, _ = self._adjust(key.start, key.stop, key.step) | |||
if start is not None and stop is not None and start > stop: | |||
stop = start | |||
return SmartSlice(self._store, start, stop, step) | |||
elif isinstance(key, int): | |||
return self._store.data[self._adjust_pos(key, True)] | |||
else: | |||
raise TypeError('list indices must be integers or slices, not ' | |||
+ type(key).__name__) | |||
def __setitem__(self, key, item): | |||
old_size = len(self._store.data) | |||
if isinstance(key, slice): | |||
start, stop, step, rng_start, rng_stop = self._adjust(key.start, key.stop, | |||
key.step, True) | |||
self._store.data[start:stop:step] = item | |||
self._update_indexes(start, stop, len(self._store.data) - old_size) | |||
elif isinstance(key, int): | |||
self._store.data[self._adjust_pos(key, True)] = item | |||
else: | |||
raise TypeError('list indices must be integers or slices, not ' | |||
+ type(key).__name__) | |||
def __delitem__(self, key): | |||
old_size = len(self._store.data) | |||
if isinstance(key, slice): | |||
start, stop, step, _, _ = self._adjust(key.start, key.stop, key.step, True) | |||
del self._store.data[start:stop:step] | |||
elif isinstance(key, int): | |||
start = stop = self._adjust_pos(key, True) | |||
del self._store.data[start] | |||
else: | |||
raise TypeError('list indices must be integers or slices, not ' | |||
+ type(key).__name__) | |||
self._update_indexes(start, stop, len(self._store.data) - old_size) | |||
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 __iter__(self): | |||
start = self._start | |||
stop = self._stop | |||
if start is None: | |||
start = 0 if self._step > 0 else (len(self._store.data) - 1) | |||
slc = SmartSlice(self._store, start, stop, self._step) | |||
while True: | |||
i = slc._start | |||
if self._step > 0: | |||
if i >= (len(self._store.data) if self._stop is None else self._stop): | |||
break | |||
elif i <= (-1 if self._stop is None else self._stop): | |||
break | |||
value = self._store.data[i] | |||
slc._start += self._step | |||
yield value | |||
def __reversed__(self): | |||
start = self._start | |||
stop = self._stop | |||
if stop is None: | |||
stop = len(self._store.data) | |||
slc = SmartSlice(self._store, start, stop, self._step) | |||
while True: | |||
i = slc._stop | |||
if self._step > 0: | |||
if i <= (0 if self._start is None else self._start): | |||
break | |||
elif i >= (len(self._store.data) if self._start is None else self._start): | |||
break | |||
value = self._store.data[i - 1] | |||
slc._stop -= self._step | |||
yield value | |||
def __repr__(self): | |||
return repr(self._render()) | |||
def __lt__(self, other): | |||
if isinstance(other, SmartSlice): | |||
return self._render() < other._render() | |||
return self._render() < other | |||
def __le__(self, other): | |||
if isinstance(other, SmartSlice): | |||
return self._render() <= other._render() | |||
return self._render() <= other | |||
def __eq__(self, other): | |||
if isinstance(other, SmartSlice): | |||
return self._render() == other._render() | |||
return self._render() == other | |||
def __ne__(self, other): | |||
if isinstance(other, SmartSlice): | |||
return self._render() != other._render() | |||
return self._render() != other | |||
def __gt__(self, other): | |||
if isinstance(other, SmartSlice): | |||
return self._render() > other._render() | |||
return self._render() > other | |||
def __ge__(self, other): | |||
if isinstance(other, SmartSlice): | |||
return self._render() >= other._render() | |||
return self._render() >= other | |||
def __bool__(self): | |||
return bool(self._render()) | |||
def __len__(self): | |||
size = len(self._store.data) if self._stop is None else self._stop | |||
if self._start is not None: | |||
size -= self._start | |||
return max(0, size // abs(self._step)) | |||
def __mul__(self, other): | |||
return smart_list(self._render() * other) | |||
def __rmul__(self, other): | |||
return smart_list(other * self._render()) | |||
def __imul__(self, other): | |||
self.extend(self._render() * (other - 1)) | |||
return self | |||
def __contains__(self, item): | |||
return item in self._render() | |||
def __add__(self, other): | |||
return smart_list(self._render() + other) | |||
def __radd__(self, other): | |||
return smart_list(other + self._render()) | |||
def __iadd__(self, other): | |||
self.extend(other) | |||
return self | |||
@inheritdoc | |||
def append(self, item): | |||
size = len(self) | |||
self[size:size] = [item] | |||
@inheritdoc | |||
def extend(self, item): | |||
size = len(self) | |||
self[size:size] = item | |||
@inheritdoc | |||
def insert(self, index, item): | |||
start, stop, step, rng_start, rng_stop = self._adjust(index, index, 1, True) | |||
self._store.data.insert(start, item) | |||
self._update_indexes(start, stop, 1) | |||
@inheritdoc | |||
def pop(self, index=None): | |||
start = 0 if self._start is None else self._start | |||
size = len(self) | |||
if index is None: | |||
index = size - 1 | |||
elif index < 0: | |||
index = size + index | |||
if index < 0 or index >= size: | |||
raise IndexError("pop index out of range") | |||
pos = start + index | |||
result = self._store.data.pop(pos) | |||
self._update_indexes(pos, pos, -1) | |||
return result | |||
@inheritdoc | |||
def remove(self, item): | |||
index = self.index(item) | |||
del self[index] | |||
@inheritdoc | |||
def reverse(self): | |||
if self._start is None and self._stop is None and self._step == 1: | |||
self._store.data.reverse() | |||
else: | |||
vals = self._render() | |||
vals.reverse() | |||
self[:] = vals | |||
# values = self._render() | |||
# self._store.detach_slice(self) | |||
# self._store = Store(values, attach=True) | |||
# self._store.attach_slice(self) | |||
# self._start = None | |||
# self._stop = None | |||
# self._step = 1 | |||
@inheritdoc | |||
def count(self, item): | |||
return self._render().count(item) | |||
@inheritdoc | |||
def index(self, item, start=None, stop=None): | |||
if start is None: | |||
return self._render().index(item) | |||
elif stop is None: | |||
return self._render().index(item, start) | |||
else: | |||
return self._render().index(item, start, stop) | |||
if py3k: | |||
@inheritdoc | |||
def sort(self, key=None, reverse=None): | |||
item = self._render() | |||
kwargs = {} | |||
if key is not None: | |||
kwargs["key"] = key | |||
if reverse is not None: | |||
kwargs["reverse"] = reverse | |||
item.sort(**kwargs) | |||
self._store.data[self._start:self._stop:self._step] = item | |||
else: | |||
@inheritdoc | |||
def sort(self, cmp=None, key=None, reverse=None): | |||
item = self._render() | |||
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 | |||
item.sort(**kwargs) | |||
self._store.data[self._start:self._stop:self._step] = item | |||
def smart_list(iterator=None): | |||
return SmartSlice(ListStore(iterator), None, None, 1) |
@@ -23,8 +23,6 @@ | |||
from __future__ import unicode_literals | |||
from sys import maxsize | |||
__all__ = [] | |||
@@ -36,19 +34,3 @@ def inheritdoc(method): | |||
""" | |||
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) |
@@ -29,7 +29,7 @@ from __future__ import unicode_literals | |||
from .compat import bytes, str | |||
from .nodes import Node | |||
from .smart_list import SmartList | |||
from .smart_list import smart_list | |||
__all__ = ["parse_anything"] | |||
@@ -53,7 +53,7 @@ def parse_anything(value, context=0, skip_style_tags=False): | |||
if isinstance(value, Wikicode): | |||
return value | |||
elif isinstance(value, Node): | |||
return Wikicode(SmartList([value])) | |||
return Wikicode(smart_list([value])) | |||
elif isinstance(value, str): | |||
return Parser().parse(value, context, skip_style_tags) | |||
elif isinstance(value, bytes): | |||
@@ -61,11 +61,11 @@ def parse_anything(value, context=0, skip_style_tags=False): | |||
elif isinstance(value, int): | |||
return Parser().parse(str(value), context, skip_style_tags) | |||
elif value is None: | |||
return Wikicode(SmartList()) | |||
return Wikicode(smart_list()) | |||
elif hasattr(value, "read"): | |||
return parse_anything(value.read(), context, skip_style_tags) | |||
try: | |||
nodelist = SmartList() | |||
nodelist = smart_list() | |||
for item in value: | |||
nodelist += parse_anything(item, context, skip_style_tags).nodes | |||
return Wikicode(nodelist) | |||
@@ -28,7 +28,6 @@ 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.ListProxy import _ListProxy | |||
from .string_mixin import StringMixIn | |||
from .utils import parse_anything | |||
@@ -113,8 +112,8 @@ class Wikicode(StringMixIn): | |||
def _is_child_wikicode(self, obj, recursive=True): | |||
"""Return whether the given :class:`.Wikicode` is a descendant.""" | |||
def deref(nodes): | |||
if isinstance(nodes, _ListProxy): | |||
return nodes._parent # pylint: disable=protected-access | |||
# if isinstance(nodes, SmartSlice) and (nodes._start is not None and nodes._stop is not None: | |||
# return nodes._parent # pylint: disable=protected-access | |||
return nodes | |||
target = deref(obj.nodes) | |||
@@ -27,10 +27,10 @@ from mwparserfromhell.compat import range | |||
from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | |||
Tag, Template, Text, Wikilink) | |||
from mwparserfromhell.nodes.extras import Attribute, Parameter | |||
from mwparserfromhell.smart_list import SmartList | |||
from mwparserfromhell.smart_list import smart_list | |||
from mwparserfromhell.wikicode import Wikicode | |||
wrap = lambda L: Wikicode(SmartList(L)) | |||
wrap = lambda L: Wikicode(smart_list(L)) | |||
wraptext = lambda *args: wrap([Text(t) for t in args]) | |||
class TreeEqualityTestCase(TestCase): | |||
@@ -25,10 +25,11 @@ from __future__ import unicode_literals | |||
import unittest | |||
from mwparserfromhell.compat import py3k, range | |||
from mwparserfromhell.smart_list import SmartList | |||
from mwparserfromhell.smart_list.ListProxy import _ListProxy | |||
from mwparserfromhell.smart_list import smart_list | |||
from mwparserfromhell.smart_list.smart_list import SmartSlice | |||
# noinspection DuplicatedCode | |||
class TestSmartList(unittest.TestCase): | |||
"""Test cases for the SmartList class and its child, _ListProxy.""" | |||
@@ -277,10 +278,10 @@ class TestSmartList(unittest.TestCase): | |||
def _dispatch_test_for_children(self, meth): | |||
"""Run a test method on various different types of children.""" | |||
meth(lambda L: SmartList(list(L))[:]) | |||
meth(lambda L: SmartList([999] + list(L))[1:]) | |||
meth(lambda L: SmartList(list(L) + [999])[:-1]) | |||
meth(lambda L: SmartList([101, 102] + list(L) + [201, 202])[2:-2]) | |||
meth(lambda lst: smart_list(list(lst))[:]) | |||
meth(lambda lst: smart_list([999] + list(lst))[1:]) | |||
meth(lambda lst: smart_list(list(lst) + [999])[:-1]) | |||
meth(lambda lst: smart_list([101, 102] + list(lst) + [201, 202])[2:-2]) | |||
def test_docs(self): | |||
"""make sure the methods of SmartList/_ListProxy have docstrings""" | |||
@@ -288,14 +289,12 @@ class TestSmartList(unittest.TestCase): | |||
"remove", "reverse", "sort"] | |||
for meth in methods: | |||
expected = getattr(list, meth).__doc__ | |||
smartlist_doc = getattr(SmartList, meth).__doc__ | |||
listproxy_doc = getattr(_ListProxy, meth).__doc__ | |||
smartlist_doc = getattr(SmartSlice, meth).__doc__ | |||
self.assertEqual(expected, smartlist_doc) | |||
self.assertEqual(expected, listproxy_doc) | |||
def test_doctest(self): | |||
"""make sure the test embedded in SmartList's docstring passes""" | |||
parent = SmartList([0, 1, 2, 3]) | |||
parent = smart_list([0, 1, 2, 3]) | |||
self.assertEqual([0, 1, 2, 3], parent) | |||
child = parent[2:] | |||
self.assertEqual([2, 3], child) | |||
@@ -305,19 +304,19 @@ class TestSmartList(unittest.TestCase): | |||
def test_parent_get_set_del(self): | |||
"""make sure SmartList's getitem/setitem/delitem work""" | |||
self._test_get_set_del_item(SmartList) | |||
self._test_get_set_del_item(smart_list) | |||
def test_parent_add(self): | |||
"""make sure SmartList's add/radd/iadd work""" | |||
self._test_add_radd_iadd(SmartList) | |||
self._test_add_radd_iadd(smart_list) | |||
def test_parent_other_magics(self): | |||
"""make sure SmartList's other magically implemented features work""" | |||
self._test_other_magic_methods(SmartList) | |||
self._test_other_magic_methods(smart_list) | |||
def test_parent_methods(self): | |||
"""make sure SmartList's non-magic methods work, like append()""" | |||
self._test_list_methods(SmartList) | |||
self._test_list_methods(smart_list) | |||
def test_child_get_set_del(self): | |||
"""make sure _ListProxy's getitem/setitem/delitem work""" | |||
@@ -337,13 +336,12 @@ class TestSmartList(unittest.TestCase): | |||
def test_influence(self): | |||
"""make sure changes are propagated from parents to children""" | |||
parent = SmartList([0, 1, 2, 3, 4, 5]) | |||
parent = smart_list([0, 1, 2, 3, 4, 5]) | |||
child1 = parent[2:] | |||
child2 = parent[2:5] | |||
self.assertEqual([0, 1, 2, 3, 4, 5], parent) | |||
self.assertEqual([2, 3, 4, 5], child1) | |||
self.assertEqual([2, 3, 4], child2) | |||
self.assertEqual(2, len(parent._children)) | |||
parent.append(6) | |||
child1.append(7) | |||
@@ -403,12 +401,10 @@ class TestSmartList(unittest.TestCase): | |||
self.assertEqual([1, 4, 3, 2, 1.9, 1.8, 5, 6], parent) | |||
self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | |||
self.assertEqual([], child3) | |||
self.assertEqual(2, len(parent._children)) | |||
del child3 | |||
self.assertEqual([1, 4, 3, 2, 1.9, 1.8, 5, 6], parent) | |||
self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | |||
self.assertEqual(1, len(parent._children)) | |||
parent.remove(1.9) | |||
parent.remove(1.8) | |||
@@ -417,8 +413,8 @@ class TestSmartList(unittest.TestCase): | |||
parent.reverse() | |||
self.assertEqual([6, 5, 2, 3, 4, 1], parent) | |||
self.assertEqual([4, 3, 2], child2) | |||
self.assertEqual(0, len(parent._children)) | |||
self.assertEqual([5, 2, 3], child2) | |||
if __name__ == "__main__": | |||
unittest.main(verbosity=2) |
@@ -29,7 +29,7 @@ import unittest | |||
from mwparserfromhell.compat import py3k, str | |||
from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | |||
Node, Tag, Template, Text, Wikilink) | |||
from mwparserfromhell.smart_list import SmartList | |||
from mwparserfromhell.smart_list import smart_list | |||
from mwparserfromhell.wikicode import Wikicode | |||
from mwparserfromhell import parse | |||
@@ -49,7 +49,7 @@ class TestWikicode(TreeEqualityTestCase): | |||
"""test getter/setter for the nodes attribute""" | |||
code = parse("Have a {{template}}") | |||
self.assertEqual(["Have a ", "{{template}}"], code.nodes) | |||
L1 = SmartList([Text("foobar"), Template(wraptext("abc"))]) | |||
L1 = smart_list([Text("foobar"), Template(wraptext("abc"))]) | |||
L2 = [Text("barfoo"), Template(wraptext("cba"))] | |||
L3 = "abc{{def}}" | |||
code.nodes = L1 | |||