Skip to content

Commit d0076ae

Browse files
committed
Fix array item grouping and removal
1 parent 1a3085c commit d0076ae

File tree

3 files changed

+63
-15
lines changed

3 files changed

+63
-15
lines changed

tests/test_items.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,3 +1019,29 @@ def test_removal_of_arrayitem_with_extra_whitespace():
10191019
docstr = doc.as_string()
10201020
parse(docstr)
10211021
assert docstr == expected
1022+
1023+
1024+
def test_badly_formatted_array_and_item_removal():
1025+
expected = """
1026+
x = [
1027+
'0'#a
1028+
,#b
1029+
'1' #c
1030+
, #d
1031+
'2' # e
1032+
,'3', # f
1033+
'4'#g
1034+
,'5' #h
1035+
,'6' ,
1036+
'7' ,#j
1037+
'8' , #k
1038+
'9' , # l
1039+
]
1040+
"""
1041+
assert expected == parse(expected).as_string()
1042+
for i in range(10):
1043+
doc = parse(expected)
1044+
x = doc["x"]
1045+
assert isinstance(x, Array)
1046+
x.remove(str(i))
1047+
parse(doc.as_string())

tomlkit/items.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,30 +1083,33 @@ def _getstate(self, protocol: int = 3) -> tuple:
10831083

10841084

10851085
class _ArrayItemGroup:
1086-
__slots__ = ("comma", "comment", "indent", "value")
1086+
__slots__ = ("comma", "comment", "comment2", "indent", "value")
10871087

10881088
def __init__(
10891089
self,
10901090
value: Item | None = None,
10911091
indent: Whitespace | None = None,
10921092
comma: Whitespace | None = None,
10931093
comment: Comment | None = None,
1094+
comment2: Comment | None = None,
10941095
) -> None:
10951096
self.value = value
10961097
self.indent = indent
10971098
self.comma = comma
10981099
self.comment = comment
1100+
self.comment2 = comment2
10991101

11001102
def __iter__(self) -> Iterator[Item]:
11011103
return filter(
1102-
lambda x: x is not None, (self.indent, self.value, self.comma, self.comment)
1104+
lambda x: x is not None,
1105+
(self.indent, self.value, self.comment, self.comma, self.comment2),
11031106
)
11041107

11051108
def __repr__(self) -> str:
11061109
return repr(tuple(self))
11071110

11081111
def is_whitespace(self) -> bool:
1109-
return self.value is None and self.comment is None
1112+
return self.value is None and self.comment is None and self.comment2 is None
11101113

11111114
def __bool__(self) -> bool:
11121115
try:
@@ -1135,10 +1138,10 @@ def __init__(
11351138
self._reindex()
11361139

11371140
def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
1138-
"""Group the values into (indent, value, comma, comment) tuples"""
1141+
"""Group the values into (indent, value, comment, comma, comment2) tuples"""
11391142
groups = []
11401143
this_group = _ArrayItemGroup()
1141-
for item in value:
1144+
for i, item in enumerate(value):
11421145
if isinstance(item, Whitespace):
11431146
if "," not in item.s:
11441147
groups.append(this_group)
@@ -1151,7 +1154,16 @@ def _group_values(self, value: list[Item]) -> list[_ArrayItemGroup]:
11511154
elif isinstance(item, Comment):
11521155
if this_group.value is None:
11531156
this_group.value = Null()
1154-
this_group.comment = item
1157+
# It's possible to have two comments per group.
1158+
# Comment one, if present, is always immediately after the value,
1159+
# and Comment two after a Whitespace with comma.
1160+
prev_item = value[i - 1]
1161+
if isinstance(prev_item, Whitespace) and "," in prev_item.s:
1162+
# If this is Comment two, this is also the end of the current group,
1163+
# but let that be done in the next pass
1164+
this_group.comment2 = item
1165+
else:
1166+
this_group.comment = item
11551167
elif this_group.value is None:
11561168
this_group.value = item
11571169
else:
@@ -1209,8 +1221,9 @@ def as_string(self) -> str:
12091221
self.trivia.indent
12101222
+ " " * 4
12111223
+ v.value.as_string()
1212-
+ ("," if not isinstance(v.value, Null) else "")
12131224
+ (v.comment.as_string() if v.comment is not None else "")
1225+
+ ("," if not isinstance(v.value, Null) else "")
1226+
+ (v.comment2.as_string() if v.comment2 is not None else "")
12141227
+ "\n"
12151228
for v in self._value
12161229
if v.value is not None
@@ -1372,6 +1385,11 @@ def insert(self, pos: int, value: Any) -> None:
13721385
# Copy the comma from the last item if 1) it contains a value and
13731386
# 2) the array is multiline
13741387
comma = last_item.comma
1388+
if last_item.comment and not last_item.comma:
1389+
# Arrays with a single value might not have a comma after the value, but may have a comment.
1390+
# Move comment one to comment two if so.
1391+
last_item.comment2 = last_item.comment
1392+
last_item.comment = None
13751393
if last_item.comma is None and not isinstance(last_item.value, Null):
13761394
# Add comma to the last item to separate it from the following items.
13771395
last_item.comma = Whitespace(",")
@@ -1408,6 +1426,14 @@ def __delitem__(self, key: int | slice):
14081426
):
14091427
# Remove the indentation of the first item if not newline
14101428
self._value[idx].indent = None
1429+
next_group = self._value[idx] if len(self._value) > idx else None
1430+
# If the previous group ends with a comment,
1431+
if self._value[idx - 1].comment2 and next_group:
1432+
# and the next group doesn't start with a newline, add/set one
1433+
if next_group.indent and "\n" not in next_group.indent.s:
1434+
next_group.indent = Whitespace("\n" + next_group.indent.s)
1435+
else:
1436+
next_group.indent = Whitespace("\n")
14111437
if len(self._value) > 0:
14121438
v = self._value[-1]
14131439
if not v.is_whitespace():

tomlkit/parser.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -595,15 +595,11 @@ def _parse_array(self) -> Array:
595595
# consume comma
596596
if prev_value and self._current == ",":
597597
self.inc(exception=UnexpectedEofError)
598-
# Check if the previous item is Whitespace
599-
if isinstance(elems[-1], Whitespace) and " " in elems[-1].s:
600-
# Preserve the previous whitespace
601-
comma = Whitespace(elems[-1].s + ",")
602-
# Remove the replaced item
603-
del elems[-1]
598+
# If the previous item is Whitespace, add to it instead of appending a new Whitespace
599+
if isinstance(elems[-1], Whitespace):
600+
elems[-1] = Whitespace(elems[-1].s + ",")
604601
else:
605-
comma = Whitespace(",")
606-
elems.append(comma)
602+
elems.append(Whitespace(","))
607603
prev_value = False
608604
continue
609605

0 commit comments

Comments
 (0)