3939CHANGELOG_FAILURE_TTL_SECONDS = 5 * 60
4040RELEASE_NOTES_MAX_CHARS = 20_000
4141
42- # CommonMark requires a space, a tab or the end of the line after the hashes: a
43- # non-breaking space copied from rich text renders as ordinary text, not a
44- # heading, but a bare `##` is an empty heading and still ends the release above.
42+ # CommonMark requires a space, tab or line end after the hashes: a non-breaking
43+ # space copied from rich text renders as text, not a heading, but a bare `##` is
44+ # an empty heading and still ends the release above.
4545_HEADING_PATTERN = re .compile (r"^ {0,3}##(?:[ \t]+(?P<title>.*?))?[ \t]*$" )
4646_FENCE_PATTERN = re .compile (r"^ {0,3}(?P<marker>`{3,}|~{3,})(?P<rest>.*)$" )
4747# CommonMark type 1 HTML blocks: contents are literal until a closing tag,
6060# Type 6 blocks run to the next blank line, so `<details>` only holds Markdown
6161# once a blank line has closed the block. Open and close tags both start one.
6262_HTML_BLOCK_OPEN = re .compile (r"^ {0,3}</?([a-zA-Z][a-zA-Z0-9-]*)(?=[\s/>]|$)" )
63- # Blocks that break into an open paragraph, so no paragraph is open after them
64- # and one they are written below is closed rather than continued.
63+ # Blocks that break into an open paragraph, so none is open after them and one
64+ # they are written below is closed rather than continued.
6565_INTERRUPTS = re .compile (
6666 r"^ {0,3}(?:#{1,6}([ \t]|$)|(?:\*[ \t]*){3,}$|(?:-[ \t]*){3,}$|(?:_[ \t]*){3,}$)"
6767)
107107_COMMENT_OPEN = "<!--"
108108_COMMENT_CLOSE = "-->"
109109# Stands in for a line the renderer hides. `#` is a block of its own, so list
110- # tracking reads it the way it reads a comment: never a marker, never a lazy
111- # paragraph continuation.
110+ # tracking reads it like a comment: never a marker, never a lazy continuation.
112111_HIDDEN_BLOCK = "#"
113112_VERSION_TOKEN_PATTERN = re .compile (r"^[\[(]?v?(?P<version>[0-9][0-9A-Za-z.!+-]*?)[\])]?$" )
114113_SAFE_VERSION_PATTERN = re .compile (r"^[0-9A-Za-z][0-9A-Za-z.!+-]{0,63}$" )
@@ -195,8 +194,8 @@ def parse_changelog(text: str) -> list[ChangelogEntry]:
195194 body : list [str ] = []
196195 open_fence : str | None = None
197196 # Content column of the list item the open block belongs to, 0 at document
198- # level. A fence and an HTML block are both scoped to their container, so
199- # the item's end closes them. Only one of the three is ever open.
197+ # level. A fence and an HTML block are scoped to their container, so the
198+ # item's end closes them. Only one of the three is ever open.
200199 block_column = 0
201200 in_comment = False
202201 in_raw_html : int | None = None
@@ -222,12 +221,11 @@ def flush() -> None:
222221 structural = ""
223222 opened_block = False
224223 in_block = open_fence is not None or in_html_block or in_raw_html is not None or in_comment
225- # A fence, a comment or an HTML block inside a list item runs only to
226- # the end of that item, so a line dedented out of the item closes both.
227- # Lazy continuation cannot reach into any of them, so any content to the
228- # left of the item ends the block along with the item. A raw block or a
229- # comment inside an item ends on a blank line as well: the item takes
230- # the break, so what follows it is a block of the item's own.
224+ # A fence, comment or HTML block inside a list item runs only to the end
225+ # of that item, so a line dedented out of the item closes both. Lazy
226+ # continuation reaches into none of them. A raw block or comment inside an
227+ # item also ends on a blank line: the item takes the break, so what
228+ # follows is a block of the item's own.
231229 leaves = (
232230 _indent_width (line ) < block_column
233231 if line .strip ()
@@ -242,9 +240,9 @@ def flush() -> None:
242240 # The paragraph the line could have continued is block content, so
243241 # it closes the item rather than reading as more of it.
244242 after_paragraph = False
245- # A fence written as a list item's first content opens inside that
246- # item, so an opener is read past a marker on the same line. Only an
247- # opener: fenced content is literal, and a closer carries no marker.
243+ # A fence written as a list item's first content opens inside that item, so
244+ # an opener is read past a marker on the same line. Only an opener: fenced
245+ # content is literal and a closer carries no marker.
248246 fence_line = line if open_fence else _item_content (line , after_paragraph )
249247 # Raw HTML first: its contents are literal, so a fence in it is not one.
250248 if in_raw_html is not None :
@@ -263,35 +261,33 @@ def flush() -> None:
263261 elif open_fence :
264262 visible = ""
265263 else :
266- # A block already open owns this line, so the line is its content
267- # rather than a block written at the column it happens to start in.
264+ # A block already open owns this line, so it is content rather than a
265+ # block written at the column it happens to start in.
268266 hidden = in_comment or in_raw_html is not None
269- # A comment is an HTML block too, so one written as a list item's
270- # first content opens inside that item exactly as a fence does: the
271- # opener is read past a marker on the same line.
267+ # A comment is an HTML block too, so one written as a list item's first
268+ # content opens inside it exactly as a fence does: the opener is read
269+ # past a marker on the same line.
272270 block_open = (
273271 not in_comment
274272 and _COMMENT_BLOCK_OPEN .match (_item_content (line , after_paragraph )) is not None
275273 )
276274 # Commented-out sections are not rendered, so they are not releases.
277275 visible , in_comment = _strip_comments (line , in_comment , block_open )
278276 # An HTML block written as a list item's first content opens inside
279- # that item, as a fence does, so an opener is read past a marker on
280- # the same line. The marker itself stays, so the item it opens is
281- # still tracked. A comment blanks its own line, so that line is read
282- # as written instead: the block renders as nothing, but the item it
283- # is the content of still opens.
277+ # that item, as a fence does, so an opener is read past a marker on the
278+ # same line. The marker stays, so its item is still tracked. A comment
279+ # blanks its own line, so that line is read as written: the block
280+ # renders as nothing, but the item it is content of still opens.
284281 source = line if block_open else visible
285282 content = _item_content (source , after_paragraph )
286283 marker = source [: len (source ) - len (content )]
287284 # Nor is anything inside a raw HTML block such as <pre>.
288285 stripped , in_raw_html = _strip_raw_html (content , in_raw_html )
289286 opened_block = in_raw_html is not None or (block_open and in_comment )
290287 # Taken before the opener is hidden: it renders as nothing, but its
291- # indentation still closes a list item it sits to the left of, and a
292- # marker on its line still opens one. A comment or a raw block keeps
293- # only those, since the text it hides is not Markdown and must not
294- # open a list of its own.
288+ # indent still closes a list item it sits left of, and a marker on its
289+ # line still opens one. A comment or raw block keeps only those, since
290+ # the text it hides is not Markdown and must open no list.
295291 if block_open or stripped != content :
296292 if not hidden :
297293 structural = _hidden_structure (line , marker )
@@ -352,21 +348,20 @@ def flush() -> None:
352348 # Only ordinary text continues a paragraph. Indented code counts four
353349 # spaces past the container, so an item's own indent does not count.
354350 indented_code = not after_paragraph and _indent_width (visible ) - column >= 4
355- # An underline ends the paragraph it underlines, so it needs one open
356- # in its own container: the quote above owns its own, and a row written
357- # left of an open item is lazy text of the item's paragraph rather than
358- # a heading. A row of three dashes is a thematic break either way, which
359- # `_INTERRUPTS` already ends the paragraph on.
351+ # An underline ends the paragraph it underlines, so it needs one open in
352+ # its own container: the quote above owns its own, and a row left of an
353+ # open item is lazy text of the item's paragraph. Three dashes are a
354+ # thematic break either way, which `_INTERRUPTS` already ends on.
360355 underline = (
361356 _SETEXT_UNDERLINE .match (visible ) is not None
362357 and after_paragraph
363358 and not quoted
364359 and _indent_width (visible ) >= column
365360 )
366361 after_paragraph = (
367- # Read inside its container, so an empty item and a fence written
368- # as an item's own content leave no paragraph open below them. A
369- # marker the paragraph above swallows is its text, not an item.
362+ # Read inside its container, so an empty item and a fence written as an
363+ # item's own content leave no paragraph open below them. A marker the
364+ # paragraph above swallows is its text, not an item.
370365 (bool (content .strip ()) or lazy_marker )
371366 and match is None
372367 and _HEADING_PATTERN .match (content ) is None
@@ -389,9 +384,9 @@ def flush() -> None:
389384 # The only paragraph a quote line leaves open is the quote's own,
390385 # and a quote holding a heading or nothing at all leaves none.
391386 after_paragraph = in_quote
392- # Whose paragraph the line below would continue. A quote owns the one
393- # its own lines hold, so a marker written outside the quote is a block
394- # of its own rather than more of the text above it.
387+ # Whose paragraph the line below would continue. A quote owns the one its
388+ # own lines hold, so a marker outside the quote is a block of its own
389+ # rather than more of the text above it.
395390 quoted = quote_line or in_quote
396391 # The lines a later underline turns into one heading. A paragraph opens
397392 # only on plain text and then runs on until something interrupts it.
@@ -412,9 +407,8 @@ def flush() -> None:
412407 continue
413408
414409 flush ()
415- # An empty heading has no title at all, so it ends the release above it
416- # without indexing one of its own: `_version_from_heading` finds no
417- # version and `flush` then skips the section.
410+ # An empty heading has no title, so it ends the release above without
411+ # indexing one: `_version_from_heading` finds no version and `flush` skips.
418412 heading = match .group ("title" ) or ""
419413 version = _version_from_heading (heading )
420414 body = []
@@ -749,8 +743,8 @@ def _strip_comments(line: str, in_comment: bool, block_open: bool) -> tuple[str,
749743 index = 0
750744 spans = _code_span_ranges (line )
751745 # Spans are ordered and disjoint and each opener sits at or past the one
752- # before, so the search resumes where it stopped. Restarting it per opener
753- # is quadratic, and a long line of code spans is reparsed on every request.
746+ # before, so the search resumes rather than restarts: restarting per opener is
747+ # quadratic, and a long line of code spans is reparsed on every request.
754748 cursor = 0
755749 while index < len (line ):
756750 opening = line .find (_COMMENT_OPEN , index )
@@ -845,8 +839,8 @@ def _item_content(line: str, after_paragraph: bool) -> str:
845839 if item is None :
846840 return line
847841 padding = _indent_width (item .group ("space" ))
848- # Over-indented content starts one column past the marker, so the rest of
849- # the padding is the content's own indentation.
842+ # Over-indented content starts one column past the marker; the rest of the
843+ # padding is the content's own indentation.
850844 over = padding - 1 if padding > _MAX_ITEM_PADDING else 0
851845 return " " * over + line [item .end () :]
852846
@@ -872,7 +866,7 @@ def _may_be_lazy(line: str) -> bool:
872866 and _INTERRUPTS .match (line ) is None
873867 and _FENCE_PATTERN .match (line ) is None
874868 # Types 1 to 6 interrupt a paragraph, so a `<div>` left of an open item
875- # closes it. Type 7 cannot, and is deliberately excluded here .
869+ # closes it. Type 7 cannot, and is deliberately excluded.
876870 and not _opens_html_block (line , True )
877871 )
878872
@@ -1020,10 +1014,9 @@ def _renders_visibly(markdown: str) -> bool:
10201014 if not in_comment and (_FENCE_PATTERN .match (line ) or opens_raw ):
10211015 # A code block or raw HTML block renders even when it is empty.
10221016 return True
1023- # No containers are tracked here, so the opener is read at the margin.
1024- # The answer does not turn on the difference: an item renders its marker
1025- # whatever the block inside it hides, so a section whose only content is
1026- # a commented-out item renders something either way.
1017+ # No containers are tracked here, so the opener is read at the margin. The
1018+ # answer does not turn on it: an item renders its marker whatever the block
1019+ # inside hides, so a commented-out item renders something either way.
10271020 visible , in_comment = _strip_comments (
10281021 line , in_comment , _COMMENT_BLOCK_OPEN .match (line ) is not None
10291022 )
0 commit comments