summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--_test/test_issues.py78
-rw-r--r--comments.py10
-rw-r--r--compat.py61
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
diff --git a/compat.py b/compat.py
index 80cb089..5a39ca5 100644
--- a/compat.py
+++ b/compat.py
@@ -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