diff --git a/tests/test_yaml_manipulation.py b/tests/test_yaml_manipulation.py index e954492..633f543 100644 --- a/tests/test_yaml_manipulation.py +++ b/tests/test_yaml_manipulation.py @@ -256,3 +256,32 @@ def test_whitespace_preservation_between_map_items(): value: placeholder """) + + +def test_whitespace_preservation_between_list_replacements(): + """Test that blank lines between list items are preserved when replacing list values.""" + s = _yml(""" +items: + - alice + + - bob + + - charlie +""") + yml = _parse(s) + + # Manipulate the YAML by changing the "bob" value to "BOB" + items_list = yml["items"] + for i, item in enumerate(items_list): + if item == "bob": + items_list[i] = "BOB" + + # The blank lines between the list items should be preserved + assert yml._to_yaml() == _yml(""" +items: + - alice + + - BOB + + - charlie +""") diff --git a/yamlium/nodes.py b/yamlium/nodes.py index 00a0f5b..9122a85 100644 --- a/yamlium/nodes.py +++ b/yamlium/nodes.py @@ -25,6 +25,15 @@ def _convert_type(obj: dict | list | str, /) -> Node: return Scalar(_value=obj) +def _preserve_metadata(old_value: Node | None, new_value: Node) -> Node: + """Copy metadata (newlines, comments) from old value to new value.""" + if old_value is not None and isinstance(old_value, Node): + new_value.newlines = old_value.newlines + new_value.inline_comments = old_value.inline_comments + new_value.stand_alone_comments = old_value.stand_alone_comments + return new_value + + class StrManipulator: """This class allows string manipulation.""" @@ -432,7 +441,10 @@ def append(self, item: Any) -> None: super().append(_convert_type(item)) def __setitem__(self, i: int, value: Any) -> None: - super().__setitem__(i, _convert_type(value)) + """Set an item in the sequence.""" + old_value = self[i] if i < len(self) else None + new_value = _preserve_metadata(old_value, _convert_type(value)) + super().__setitem__(i, new_value) def extend(self, items: list) -> None: """Extend the sequence with multiple items. @@ -501,23 +513,12 @@ def _to_yaml(self, i: int = 0) -> str: return result def __setitem__(self, key: Key | str, value: Any) -> None: - """Set a key-value pair in the mapping. - - Args: - key: The key to set. Can be a Key Any or string. - value: The value to set. Will be converted to a Node if it isn't one. - """ + """Set a key-value pair in the mapping.""" if isinstance(key, str): key = Key(_value=key) - # Preserve newlines and comments from old value if it exists old_value = self.get(key) - new_value = _convert_type(value) - if old_value is not None and isinstance(old_value, Node): - new_value.newlines = old_value.newlines - new_value.inline_comments = old_value.inline_comments - new_value.stand_alone_comments = old_value.stand_alone_comments - + new_value = _preserve_metadata(old_value, _convert_type(value)) super().__setitem__(key, new_value) def update(self, other: dict) -> None: