diff options
-rw-r--r-- | _test/test_issues.py | 78 | ||||
-rw-r--r-- | comments.py | 10 | ||||
-rw-r--r-- | compat.py | 61 |
3 files changed, 143 insertions, 6 deletions
diff --git a/_test/test_issues.py b/_test/test_issues.py index 9539dc5..ce8440a 100644 --- a/_test/test_issues.py +++ b/_test/test_issues.py @@ -80,7 +80,7 @@ class TestIssues: assert save_and_run(program_src, tmpdir) == 0 def test_issue_82rt(self, tmpdir): - yaml_str = "[1, 2, 3, !si 10k, 100G]\n" + yaml_str = '[1, 2, 3, !si 10k, 100G]\n' x = round_trip(yaml_str, preserve_quotes=True) # NOQA def test_issue_102(self): @@ -145,6 +145,82 @@ class TestIssues: x = round_trip_load(TestIssues.json_str2) # NOQA x = round_trip_load(TestIssues.json_str) # NOQA + def test_issue_176(self): + # basic request by Stuart Berg + from ruamel.yaml import YAML + + yaml = YAML() + seq = yaml.load('[1,2,3]') + seq[:] = [1, 2, 3, 4] + + def test_issue_176_preserve_comments_on_extended_slice_assignment(self): + yaml_str = dedent("""\ + - a + - b # comment + - c # commment c + # comment c+ + - d + + - e # comment + """) + seq = round_trip_load(yaml_str) + seq[1::2] = ['B', 'D'] + res = round_trip_dump(seq) + assert res == yaml_str.replace(' b ', ' B ').replace(' d\n', ' D\n') + + def test_issue_176_test_slicing(self): + from ruamel.yaml.comments import CommentedSeq + + mss = round_trip_load('[0, 1, 2, 3, 4]') + assert len(mss) == 5 + assert mss[2:2] == [] + assert mss[2:4] == [2, 3] + assert isinstance(mss[2:4], CommentedSeq) + assert mss[1::2] == [1, 3] + + # slice assignment + m = mss[:] + m[2:2] = [42] + assert m == [0, 1, 42, 2, 3, 4] + + m = mss[:] + m[:3] = [42, 43, 44] + assert m == [42, 43, 44, 3, 4] + m = mss[:] + m[2:] = [42, 43, 44] + assert m == [0, 1, 42, 43, 44] + m = mss[:] + m[:] = [42, 43, 44] + assert m == [42, 43, 44] + + # extend slice assignment + m = mss[:] + m[2:4] = [42, 43, 44] + assert m == [0, 1, 42, 43, 44, 4] + m = mss[:] + m[1::2] = [42, 43] + assert m == [0, 42, 2, 43, 4] + m = mss[:] + with pytest.raises(TypeError, match='too many'): + m[1::2] = [42, 43, 44] + with pytest.raises(TypeError, match='not enough'): + m[1::2] = [42] + m = mss[:] + m += [5] + m[1::2] = [42, 43, 44] + assert m == [0, 42, 2, 43, 4, 44] + + # deleting + m = mss[:] + del m[1:3] + assert m == [0, 3, 4] + m = mss[:] + del m[::2] + assert m == [1, 3] + m = mss[:] + del m[:] + assert m == [] + def test_issue_184(self): yaml_str = dedent("""\ test::test: diff --git a/comments.py b/comments.py index 2063775..283a943 100644 --- a/comments.py +++ b/comments.py @@ -12,7 +12,7 @@ import sys import copy -from ruamel.yaml.compat import ordereddict, PY2, string_types, MutableSequence +from ruamel.yaml.compat import ordereddict, PY2, string_types, MutableSliceableSequence from ruamel.yaml.scalarstring import ScalarString if PY2: @@ -385,18 +385,18 @@ class CommentedBase(object): raise NotImplementedError -class CommentedSeq(MutableSequence, CommentedBase): +class CommentedSeq(MutableSliceableSequence, CommentedBase): __slots__ = (Comment.attrib, '_lst') def __init__(self, *args, **kw): # type: (Any, Any) -> None self._lst = list(*args, **kw) - def __getitem__(self, idx): + def __getsingleitem__(self, idx): # type: (Any) -> Any return self._lst[idx] - def __setitem__(self, idx, value): + def __setsingleitem__(self, idx, value): # type: (Any, Any) -> None # try to preserve the scalarstring type if setting an existing key to a new value if idx < len(self): @@ -408,7 +408,7 @@ class CommentedSeq(MutableSequence, CommentedBase): value = type(self[idx])(value) self._lst.__setitem__(idx, value) - def __delitem__(self, idx=None): + def __delsingleitem__(self, idx=None): # type: (Any) -> Any del self._lst[idx] self.ca.items.pop(idx, None) # might not be there -> default value @@ -7,6 +7,7 @@ from __future__ import print_function import sys import os import types +from abc import abstractmethod # fmt: off if False: # MYPY @@ -220,3 +221,63 @@ def version_tnf(t1, t2=None): if t2 is not None and version_info < t2: return None return False + + +class MutableSliceableSequence(MutableSequence): + __slots__ = () + + def __getitem__(self, index): + # type: (Any) -> Any + if not isinstance(index, slice): + return self.__getsingleitem__(index) + return type(self)([self[i] for i in range(*index.indices(len(self)))]) # type: ignore + + def __setitem__(self, index, value): + # type: (Any, Any) -> None + if not isinstance(index, slice): + return self.__setsingleitem__(index, value) + assert iter(value) + print(index.start, index.stop, index.step, index.indices(len(self))) + if index.step is None: + del self[index.start : index.stop] + for elem in reversed(value): + self.insert(0 if index.start is None else index.start, elem) + else: + range_parms = index.indices(len(self)) + nr_assigned_items = (range_parms[1] - range_parms[0] - 1) // range_parms[2] + 1 + # need to test before changing, in case TypeError is caught + if nr_assigned_items < len(value): + raise TypeError( + 'too many elements in value {} < {}'.format(nr_assigned_items, len(value)) + ) + elif nr_assigned_items > len(value): + raise TypeError( + 'not enough elements in value {} > {}'.format( + nr_assigned_items, len(value) + ) + ) + for idx, i in enumerate(range(*range_parms)): + self[i] = value[idx] + + def __delitem__(self, index): + # type: (Any) -> None + if not isinstance(index, slice): + return self.__delsingleitem__(index) + print(index.start, index.stop, index.step, index.indices(len(self))) + for i in reversed(range(*index.indices(len(self)))): + del self[i] + + @abstractmethod + def __getsingleitem__(self, index): + # type: (Any) -> Any + raise IndexError + + @abstractmethod + def __setsingleitem__(self, index, value): + # type: (Any, Any) -> None + raise IndexError + + @abstractmethod + def __delsingleitem__(self, index): + # type: (Any) -> None + raise IndexError |