| @@ -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 | |||