Skip to content

Commit d4813dc

Browse files
committed
fixup! Use set instead of list for block level elements
1 parent 1bc8c54 commit d4813dc

File tree

1 file changed

+255
-47
lines changed

1 file changed

+255
-47
lines changed

markdown/util.py

Lines changed: 255 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
from __future__ import annotations
2626

27-
from collections.abc import Callable
2827
import re
2928
import sys
3029
import warnings
@@ -45,60 +44,269 @@
4544
"""
4645

4746

48-
class _BlockLevelElements(set):
49-
# ----------------------------------
50-
# Methods common to `list` and `set`
51-
# ----------------------------------
52-
def copy(self) -> set[str]:
53-
return _BlockLevelElements(super().copy())
47+
# TODO: Raise errors from list methods in the future.
48+
# Later, remove this class entirely and use a regular set.
49+
class _BlockLevelElements:
50+
def __init__(self, elements: list[str]) -> None:
51+
self._list = elements.copy()
52+
self._set = set(self._list)
53+
54+
def __add__(self, other: list[str]) -> list[str]:
55+
warnings.warn(
56+
"Using block level elements as a list is deprecated, use it as a set instead.",
57+
DeprecationWarning,
58+
)
59+
# Using `+` means user expects a list back.
60+
return self._list + other
61+
62+
def __and__(self, other: set[str]) -> set[str]:
63+
# Using `&` means user expects a set back.
64+
return self._set & other
65+
66+
def __contains__(self, item):
67+
return item in self._set
68+
69+
def __delitem__(self, key):
70+
warnings.warn(
71+
"Using block level elements as a list is deprecated, use it as a set instead.",
72+
DeprecationWarning,
73+
)
74+
element = self._list[key]
75+
del self._list[key]
76+
self._set.remove(element)
77+
78+
def __getitem__(self, index):
79+
warnings.warn(
80+
"Using block level elements as a list is deprecated, use it as a set instead.",
81+
DeprecationWarning,
82+
)
83+
return self._list[index]
84+
85+
def __iadd__(self, other: list[str]) -> None:
86+
warnings.warn(
87+
"Using block level elements as a list is deprecated, use it as a set instead.",
88+
DeprecationWarning,
89+
)
90+
# In-place addition should update both list and set.
91+
self._list += other
92+
self._set.update(set(other))
93+
94+
def __iand__(self, other: set[str]) -> None:
95+
# In-place intersection should update both list and set.
96+
self._list = [element for element in self._list if element in other]
97+
self._set &= other
98+
99+
def __ior__(self, other: set[str]) -> None:
100+
# In-place union should update both list and set.
101+
for element in other:
102+
if element not in self._set:
103+
self._list.append(element)
104+
self._set |= other
105+
106+
def __iter__(self) -> Iterator[str]:
107+
return iter(self._list)
54108

55-
def pop(self, index: int | None = None, /) -> str:
56-
if index is not None:
57-
warnings.warn("The index argument is deprecated and will be removed in the future", DeprecationWarning)
58-
try:
59-
return self.pop()
60-
except KeyError:
61-
warnings.warn("`pop` will raise a `KeyError` in the future", DeprecationWarning)
62-
raise IndexError("pop from an empty set") from None
109+
def __len__(self) -> int:
110+
# Length of the list, for backwards compatibility.
111+
# If used as a set, both lengths will be the same.
112+
return len(self._list)
113+
114+
def __or__(self, value: set[str]) -> set[str]:
115+
# Using `|` means user expects a set back.
116+
return self._set | value
117+
118+
def __rand__(self, value: set[str]) -> set[str]:
119+
# Using `&` means user expects a set back.
120+
return value & self._set
121+
122+
def __ror__(self, value: set[str]) -> set[str]:
123+
# Using `|` means user expects a set back.
124+
return value | self._set
125+
126+
def __rsub__(self, value: set[str]) -> set[str]:
127+
# Using `-` means user expects a set back.
128+
return value - self._set
129+
130+
def __rxor__(self, value: set[str]) -> set[str]:
131+
# Using `^` means user expects a set back.
132+
return value ^ self._set
133+
134+
def __sub__(self, value: set[str]) -> set[str]:
135+
# Using `-` means user expects a set back.
136+
return self._set - value
137+
138+
def __xor__(self, value: set[str]) -> set[str]:
139+
# Using `^` means user expects a set back.
140+
return self._set ^ value
141+
142+
def __reversed__(self) -> Iterator[str]:
143+
warnings.warn(
144+
"Using block level elements as a list is deprecated, use it as a set instead.",
145+
DeprecationWarning,
146+
)
147+
return reversed(self._list)
148+
149+
def __setitem__(self, key: int, value: str) -> None:
150+
warnings.warn(
151+
"Using block level elements as a list is deprecated, use it as a set instead.",
152+
DeprecationWarning,
153+
)
154+
# In-place item-setting should update both list and set.
155+
self._list[key] = value
156+
self._set.add(value)
157+
158+
def __str__(self) -> str:
159+
return str(self._set)
160+
161+
def add(self, element: str) -> None:
162+
# In-place addition should update both list and set.
163+
self._set.add(element)
164+
self._list.append(element)
63165

64-
def remove(self, element: object) -> None:
65-
try:
66-
return self.remove(element)
67-
except KeyError:
68-
warnings.warn("`remove` will raise a `KeyError` in the future", DeprecationWarning)
69-
raise ValueError(f"{element!r} not in set") from None
70-
71-
# --------------------------
72-
# Methods specific to `list`
73-
# --------------------------
74166
def append(self, element: str) -> None:
75-
warnings.warn("method `append` will be removed in the future", DeprecationWarning)
76-
self.add(element)
167+
warnings.warn(
168+
"Using block level elements as a list is deprecated, use it as a set instead.",
169+
DeprecationWarning,
170+
)
171+
# In-place addition should update both list and set.
172+
self._list.append(element)
173+
self._set.add(element)
174+
175+
def clear(self) -> None:
176+
self._list.clear()
177+
self._set.clear()
178+
179+
def copy(self) -> _BlockLevelElements:
180+
# We're not sure yet whether the user wants to use it as a set or list.
181+
return _BlockLevelElements(self._list)
77182

78183
def count(self, value: str) -> int:
79-
warnings.warn("method `count` will be removed in the future", DeprecationWarning)
80-
return 1 if value in self else 0
184+
warnings.warn(
185+
"Using block level elements as a list is deprecated, use it as a set instead.",
186+
DeprecationWarning,
187+
)
188+
# Count in list, for backwards compatibility.
189+
# If used as a set, both counts will be the same (1).
190+
return self._list.count(value)
191+
192+
def difference(self, *others: set[str]) -> set[str]:
193+
# User expects a set back.
194+
return self._set.difference(*others)
195+
196+
def difference_update(self, *others) -> None:
197+
# In-place difference should update both list and set.
198+
self._set.difference_update(*others)
199+
for other in others:
200+
for element in other:
201+
self._list.remove(element)
202+
203+
def discard(self, element: str) -> None:
204+
# In-place discard should update both list and set.
205+
self._set.discard(element)
206+
while True:
207+
try:
208+
self._list.remove(element)
209+
except ValueError:
210+
break
81211

82212
def extend(self, elements: list[str]) -> None:
83-
warnings.warn("method `extend` will be removed in the future", DeprecationWarning)
84-
self.update(elements)
85-
86-
def index(self, value, start=0, stop=0, /) -> int:
87-
warnings.warn("method `index` will be removed in the future", DeprecationWarning)
88-
return 0 if value in self else -1
89-
90-
def insert(self, index: int, element: str) -> None:
91-
warnings.warn("method `insert` will be removed in the future", DeprecationWarning)
92-
self.add(element)
213+
warnings.warn(
214+
"Using block level elements as a list is deprecated, use it as a set instead.",
215+
DeprecationWarning,
216+
)
217+
# In-place extension should update both list and set.
218+
self._list.extend(elements)
219+
self._set.update(elements)
220+
221+
def index(self, value, start: int = 0, stop: int = sys.maxsize, /):
222+
warnings.warn(
223+
"Using block level elements as a list is deprecated, use it as a set instead.",
224+
DeprecationWarning,
225+
)
226+
return self._list.index(value, start, stop)
227+
228+
def insert(self, index: int, element: str, /) -> None:
229+
warnings.warn(
230+
"Using block level elements as a list is deprecated, use it as a set instead.",
231+
DeprecationWarning,
232+
)
233+
# In-place insertion should update both list and set.
234+
self._list.insert(index, element)
235+
self._set.add(element)
236+
237+
def intersection(self, *others: set[str]) -> set[str]:
238+
# User expects a set back.
239+
return self._set.intersection(*others)
240+
241+
def intersection_update(self, *others: set[str]) -> None:
242+
# In-place intersection should update both list and set.
243+
self._set.intersection_update(*others)
244+
for element in reversed(self._list):
245+
if element not in self._set:
246+
self._list.remove(element)
247+
248+
def isdisjoint(self, other: set[str]) -> bool:
249+
return self._set.isdisjoint(other)
250+
251+
def issubset(self, other: set[str]) -> bool:
252+
return self._set.issubset(other)
253+
254+
def issuperset(self, other: set[str]) -> bool:
255+
return self._set.issuperset(other)
256+
257+
def pop(self, index: int = -1, /) -> str:
258+
# In-place pop should update both list and set.
259+
element = self._list.pop(index)
260+
self._set.remove(element)
261+
return element
262+
263+
def remove(self, element: str) -> None:
264+
# In-place removal should update both list and set.
265+
self._list.remove(element)
266+
self._set.remove(element)
93267

94268
def reverse(self) -> None:
95-
warnings.warn("method `reverse` will be removed in the future", DeprecationWarning)
96-
97-
def sort(self, /, *, key: Callable | None = None, reverse: bool = False) -> None:
98-
warnings.warn("method `sort` will be removed in the future", DeprecationWarning)
99-
100-
101-
BLOCK_LEVEL_ELEMENTS: set[str] = _BlockLevelElements({
269+
warnings.warn(
270+
"Using block level elements as a list is deprecated, use it as a set instead.",
271+
DeprecationWarning,
272+
)
273+
self._list.reverse()
274+
275+
def sort(self, /, *, key=None, reverse=False) -> None:
276+
warnings.warn(
277+
"Using block level elements as a list is deprecated, use it as a set instead.",
278+
DeprecationWarning,
279+
)
280+
self._list.sort(key=key, reverse=reverse)
281+
282+
def symmetric_difference(self, other: set[str]) -> set[str]:
283+
# User expects a set back.
284+
return self._set.symmetric_difference(other)
285+
286+
def symmetric_difference_update(self, other: set[str]) -> None:
287+
# In-place symmetric difference should update both list and set.
288+
self._set.symmetric_difference_update(other)
289+
for element in other:
290+
if element in self._set:
291+
self._list.remove(element)
292+
else:
293+
self._list.append(element)
294+
295+
def union(self, *others: set[str]) -> set[str]:
296+
# User expects a set back.
297+
return self._set.union(*others)
298+
299+
def update(self, *others: set[str]) -> None:
300+
# In-place union should update both list and set.
301+
self._set.update(*others)
302+
for other in others:
303+
for element in other:
304+
if element not in self._set:
305+
self._list.append(element)
306+
307+
308+
# Type it as `set[str]` to express our intent for it to be used as such.
309+
BLOCK_LEVEL_ELEMENTS: set[str] = _BlockLevelElements([
102310
# Elements which are invalid to wrap in a `<p>` tag.
103311
# See https://w3c.github.io/html/grouping-content.html#the-p-element
104312
'address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl',
@@ -110,7 +318,7 @@ def sort(self, /, *, key: Callable | None = None, reverse: bool = False) -> None
110318
'math', 'map', 'noscript', 'output', 'object', 'option', 'progress', 'script',
111319
'style', 'summary', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'video',
112320
'center'
113-
})
321+
]) # type: ignore[assignment]
114322
"""
115323
Set of HTML tags which get treated as block-level elements. Same as the `block_level_elements`
116324
attribute of the [`Markdown`][markdown.Markdown] class. Generally one should use the

0 commit comments

Comments
 (0)