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, | from ..nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, Tag, | ||||
| Template, Text, Wikilink) | Template, Text, Wikilink) | ||||
| from ..nodes.extras import Attribute, Parameter | from ..nodes.extras import Attribute, Parameter | ||||
| from ..smart_list import SmartList | |||||
| from ..smart_list import smart_list | |||||
| from ..wikicode import Wikicode | from ..wikicode import Wikicode | ||||
| __all__ = ["Builder"] | __all__ = ["Builder"] | ||||
| @@ -67,7 +67,7 @@ class Builder(object): | |||||
| The raw node list is wrapped in a :class:`.SmartList` and then in a | The raw node list is wrapped in a :class:`.SmartList` and then in a | ||||
| :class:`.Wikicode` object. | :class:`.Wikicode` object. | ||||
| """ | """ | ||||
| return Wikicode(SmartList(self._stacks.pop())) | |||||
| return Wikicode(smart_list(self._stacks.pop())) | |||||
| def _write(self, item): | def _write(self, item): | ||||
| """Append a node to the current node list.""" | """Append a node to the current node list.""" | ||||
| @@ -92,7 +92,7 @@ class Builder(object): | |||||
| self._tokens.append(token) | self._tokens.append(token) | ||||
| value = self._pop() | value = self._pop() | ||||
| if key is None: | if key is None: | ||||
| key = Wikicode(SmartList([Text(str(default))])) | |||||
| key = Wikicode(smart_list([Text(str(default))])) | |||||
| return Parameter(key, value, showkey) | return Parameter(key, value, showkey) | ||||
| else: | else: | ||||
| self._write(self._handle_token(token)) | 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. | 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 __future__ import unicode_literals | ||||
| from sys import maxsize | |||||
| __all__ = [] | __all__ = [] | ||||
| @@ -36,19 +34,3 @@ def inheritdoc(method): | |||||
| """ | """ | ||||
| method.__doc__ = getattr(list, method.__name__).__doc__ | method.__doc__ = getattr(list, method.__name__).__doc__ | ||||
| return method | 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 .compat import bytes, str | ||||
| from .nodes import Node | from .nodes import Node | ||||
| from .smart_list import SmartList | |||||
| from .smart_list import smart_list | |||||
| __all__ = ["parse_anything"] | __all__ = ["parse_anything"] | ||||
| @@ -53,7 +53,7 @@ def parse_anything(value, context=0, skip_style_tags=False): | |||||
| if isinstance(value, Wikicode): | if isinstance(value, Wikicode): | ||||
| return value | return value | ||||
| elif isinstance(value, Node): | elif isinstance(value, Node): | ||||
| return Wikicode(SmartList([value])) | |||||
| return Wikicode(smart_list([value])) | |||||
| elif isinstance(value, str): | elif isinstance(value, str): | ||||
| return Parser().parse(value, context, skip_style_tags) | return Parser().parse(value, context, skip_style_tags) | ||||
| elif isinstance(value, bytes): | elif isinstance(value, bytes): | ||||
| @@ -61,11 +61,11 @@ def parse_anything(value, context=0, skip_style_tags=False): | |||||
| elif isinstance(value, int): | elif isinstance(value, int): | ||||
| return Parser().parse(str(value), context, skip_style_tags) | return Parser().parse(str(value), context, skip_style_tags) | ||||
| elif value is None: | elif value is None: | ||||
| return Wikicode(SmartList()) | |||||
| return Wikicode(smart_list()) | |||||
| elif hasattr(value, "read"): | elif hasattr(value, "read"): | ||||
| return parse_anything(value.read(), context, skip_style_tags) | return parse_anything(value.read(), context, skip_style_tags) | ||||
| try: | try: | ||||
| nodelist = SmartList() | |||||
| nodelist = smart_list() | |||||
| for item in value: | for item in value: | ||||
| nodelist += parse_anything(item, context, skip_style_tags).nodes | nodelist += parse_anything(item, context, skip_style_tags).nodes | ||||
| return Wikicode(nodelist) | return Wikicode(nodelist) | ||||
| @@ -28,7 +28,6 @@ from itertools import chain | |||||
| from .compat import bytes, py3k, range, str | from .compat import bytes, py3k, range, str | ||||
| from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | from .nodes import (Argument, Comment, ExternalLink, Heading, HTMLEntity, | ||||
| Node, Tag, Template, Text, Wikilink) | Node, Tag, Template, Text, Wikilink) | ||||
| from .smart_list.ListProxy import _ListProxy | |||||
| from .string_mixin import StringMixIn | from .string_mixin import StringMixIn | ||||
| from .utils import parse_anything | from .utils import parse_anything | ||||
| @@ -113,8 +112,8 @@ class Wikicode(StringMixIn): | |||||
| def _is_child_wikicode(self, obj, recursive=True): | def _is_child_wikicode(self, obj, recursive=True): | ||||
| """Return whether the given :class:`.Wikicode` is a descendant.""" | """Return whether the given :class:`.Wikicode` is a descendant.""" | ||||
| def deref(nodes): | 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 | return nodes | ||||
| target = deref(obj.nodes) | target = deref(obj.nodes) | ||||
| @@ -27,10 +27,10 @@ from mwparserfromhell.compat import range | |||||
| from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | ||||
| Tag, Template, Text, Wikilink) | Tag, Template, Text, Wikilink) | ||||
| from mwparserfromhell.nodes.extras import Attribute, Parameter | 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 | 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]) | wraptext = lambda *args: wrap([Text(t) for t in args]) | ||||
| class TreeEqualityTestCase(TestCase): | class TreeEqualityTestCase(TestCase): | ||||
| @@ -25,10 +25,11 @@ from __future__ import unicode_literals | |||||
| import unittest | import unittest | ||||
| from mwparserfromhell.compat import py3k, range | from mwparserfromhell.compat import py3k, range | ||||
| from mwparserfromhell.smart_list import SmartList | |||||
| 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): | class TestSmartList(unittest.TestCase): | ||||
| """Test cases for the SmartList class and its child, _ListProxy.""" | """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): | def _dispatch_test_for_children(self, meth): | ||||
| """Run a test method on various different types of children.""" | """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): | def test_docs(self): | ||||
| """make sure the methods of SmartList/_ListProxy have docstrings""" | """make sure the methods of SmartList/_ListProxy have docstrings""" | ||||
| @@ -288,14 +289,12 @@ class TestSmartList(unittest.TestCase): | |||||
| "remove", "reverse", "sort"] | "remove", "reverse", "sort"] | ||||
| for meth in methods: | for meth in methods: | ||||
| expected = getattr(list, meth).__doc__ | 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, smartlist_doc) | ||||
| self.assertEqual(expected, listproxy_doc) | |||||
| def test_doctest(self): | def test_doctest(self): | ||||
| """make sure the test embedded in SmartList's docstring passes""" | """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) | self.assertEqual([0, 1, 2, 3], parent) | ||||
| child = parent[2:] | child = parent[2:] | ||||
| self.assertEqual([2, 3], child) | self.assertEqual([2, 3], child) | ||||
| @@ -305,19 +304,19 @@ class TestSmartList(unittest.TestCase): | |||||
| def test_parent_get_set_del(self): | def test_parent_get_set_del(self): | ||||
| """make sure SmartList's getitem/setitem/delitem work""" | """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): | def test_parent_add(self): | ||||
| """make sure SmartList's add/radd/iadd work""" | """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): | def test_parent_other_magics(self): | ||||
| """make sure SmartList's other magically implemented features work""" | """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): | def test_parent_methods(self): | ||||
| """make sure SmartList's non-magic methods work, like append()""" | """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): | def test_child_get_set_del(self): | ||||
| """make sure _ListProxy's getitem/setitem/delitem work""" | """make sure _ListProxy's getitem/setitem/delitem work""" | ||||
| @@ -337,13 +336,12 @@ class TestSmartList(unittest.TestCase): | |||||
| def test_influence(self): | def test_influence(self): | ||||
| """make sure changes are propagated from parents to children""" | """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:] | child1 = parent[2:] | ||||
| child2 = parent[2:5] | child2 = parent[2:5] | ||||
| self.assertEqual([0, 1, 2, 3, 4, 5], parent) | self.assertEqual([0, 1, 2, 3, 4, 5], parent) | ||||
| self.assertEqual([2, 3, 4, 5], child1) | self.assertEqual([2, 3, 4, 5], child1) | ||||
| self.assertEqual([2, 3, 4], child2) | self.assertEqual([2, 3, 4], child2) | ||||
| self.assertEqual(2, len(parent._children)) | |||||
| parent.append(6) | parent.append(6) | ||||
| child1.append(7) | 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([1, 4, 3, 2, 1.9, 1.8, 5, 6], parent) | ||||
| self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | self.assertEqual([4, 3, 2, 1.9, 1.8], child2) | ||||
| self.assertEqual([], child3) | self.assertEqual([], child3) | ||||
| self.assertEqual(2, len(parent._children)) | |||||
| del child3 | del child3 | ||||
| self.assertEqual([1, 4, 3, 2, 1.9, 1.8, 5, 6], parent) | 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([4, 3, 2, 1.9, 1.8], child2) | ||||
| self.assertEqual(1, len(parent._children)) | |||||
| parent.remove(1.9) | parent.remove(1.9) | ||||
| parent.remove(1.8) | parent.remove(1.8) | ||||
| @@ -417,8 +413,8 @@ class TestSmartList(unittest.TestCase): | |||||
| parent.reverse() | parent.reverse() | ||||
| self.assertEqual([6, 5, 2, 3, 4, 1], parent) | 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__": | if __name__ == "__main__": | ||||
| unittest.main(verbosity=2) | unittest.main(verbosity=2) | ||||
| @@ -29,7 +29,7 @@ import unittest | |||||
| from mwparserfromhell.compat import py3k, str | from mwparserfromhell.compat import py3k, str | ||||
| from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | from mwparserfromhell.nodes import (Argument, Comment, Heading, HTMLEntity, | ||||
| Node, Tag, Template, Text, Wikilink) | 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.wikicode import Wikicode | ||||
| from mwparserfromhell import parse | from mwparserfromhell import parse | ||||
| @@ -49,7 +49,7 @@ class TestWikicode(TreeEqualityTestCase): | |||||
| """test getter/setter for the nodes attribute""" | """test getter/setter for the nodes attribute""" | ||||
| code = parse("Have a {{template}}") | code = parse("Have a {{template}}") | ||||
| self.assertEqual(["Have a ", "{{template}}"], code.nodes) | 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"))] | L2 = [Text("barfoo"), Template(wraptext("cba"))] | ||||
| L3 = "abc{{def}}" | L3 = "abc{{def}}" | ||||
| code.nodes = L1 | code.nodes = L1 | ||||