Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-mistletoe for 
openSUSE:Factory checked in at 2026-10-14 18:33:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-mistletoe (Old)
 and      /work/SRC/openSUSE:Factory/.python-mistletoe.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-mistletoe"

Wed Oct 14 18:33:22 2026 rev:7 rq:1384372 version:1.6.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-mistletoe/python-mistletoe.changes        
2026-03-17 19:04:25.708499961 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-mistletoe.new.3698736/python-mistletoe.changes
   2026-10-14 18:33:37.100058093 +0200
@@ -1,0 +2,18 @@
+Mon Oct  5 16:25:52 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.6.0:
+  * Incorrect escaping of `$` and other reserved chars in links
+    (#248).
+  * Fix `` being rendered as `...` (#260).
+  * `HtmlRenderer`: Escape characters in image src.
+  * `MarkdownRenderer`: Fix type annotations (#265, #267).
+  * `JiraRenderer`: Escape pipes in table cells (#270).
+  * `MarkdownRenderer`: Escape pipes in table cells (#274).
+  * `MarkdownRenderer`: Preserve line break before/after HTML
+    span during word-wrap (#275).
+  * Avoid crash on overlapping emphasis delimiters (#271). 🎉
+  * `GithubWiki`: Reduce backtracking in wiki links (#276).
+  * Guard `remove_token()` against duplicate removal (#281).
+  * `HtmlRenderer`: Escape mailto autolink hrefs (#283).
+
+-------------------------------------------------------------------

Old:
----
  mistletoe-1.5.1.tar.gz

New:
----
  mistletoe-1.6.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-mistletoe.spec ++++++
--- /var/tmp/diff_new_pack.nveh8A/_old  2026-10-14 18:33:37.852089938 +0200
+++ /var/tmp/diff_new_pack.nveh8A/_new  2026-10-14 18:33:37.854090022 +0200
@@ -23,7 +23,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-mistletoe
-Version:        1.5.1
+Version:        1.6.0
 Release:        0
 Summary:        A Markdown parser in pure Python
 License:        MIT

++++++ mistletoe-1.5.1.tar.gz -> mistletoe-1.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/.github/workflows/python-package.yml 
new/mistletoe-1.6.0/.github/workflows/python-package.yml
--- old/mistletoe-1.5.1/.github/workflows/python-package.yml    2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/.github/workflows/python-package.yml    2026-07-11 
16:09:13.000000000 +0200
@@ -18,12 +18,13 @@
     # As long as we want to support Python 3.6-, we need to stick to an older 
version of Ubuntu,
     # see <https://github.com/actions/setup-python/issues/544>.
     #
-    # Update: ubuntu-20.04 is no longer available, see 
<https://github.com/actions/runner-images/issues/11101>.
+    # UPDATE: ubuntu-20.04 is no longer available, see 
<https://github.com/actions/runner-images/issues/11101>.
+    # So we also need to remove versions 3.5 and 3.6 from the list to avoid 
this CI error: "The version 'X.Y' with architecture 'x64' was not found for 
Ubuntu 22.04."
     runs-on: ubuntu-22.04
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", 
"3.14"]
 
     steps:
     - uses: actions/checkout@v3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/dev-guide.md 
new/mistletoe-1.6.0/dev-guide.md
--- old/mistletoe-1.5.1/dev-guide.md    2025-12-07 17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/dev-guide.md    2026-07-11 16:09:13.000000000 +0200
@@ -307,7 +307,7 @@
     if isinstance(token, RawText):
         token.content = token.content.replace("mistletoe", "The Amazing 
mistletoe")
 
-    if not isinstance(token, InlineCode) and hasattr(token, "children"):
+    if not isinstance(token, InlineCode) and token.children is not None:
         for child in token.children:
             update_text(child)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/__init__.py 
new/mistletoe-1.6.0/mistletoe/__init__.py
--- old/mistletoe-1.5.1/mistletoe/__init__.py   2025-12-07 17:12:47.000000000 
+0100
+++ new/mistletoe-1.6.0/mistletoe/__init__.py   2026-07-11 16:09:13.000000000 
+0200
@@ -2,7 +2,7 @@
 Make mistletoe easier to import.
 """
 
-__version__ = "1.5.1"
+__version__ = "1.6.0"
 __all__ = ['html_renderer', 'ast_renderer', 'block_token', 'block_tokenizer',
            'span_token', 'span_tokenizer']
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/base_renderer.py 
new/mistletoe-1.6.0/mistletoe/base_renderer.py
--- old/mistletoe-1.5.1/mistletoe/base_renderer.py      2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/base_renderer.py      2026-07-11 
16:09:13.000000000 +0200
@@ -203,3 +203,9 @@
 
     def render_document(self, token: block_token.Document) -> str:
         return self.render_inner(token)
+
+
+# Safe characters for URL quoting. These are basically all the URI
+# reserved characters as per [RFC 
3986](https://www.rfc-editor.org/rfc/rfc3986#section-2.2).
+# Plus we add the percent character (%) to avoid double-escaping.
+URI_SAFE_CHARACTERS = ":/?#[]@!$&'()*+,;=%"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/block_token.py 
new/mistletoe-1.6.0/mistletoe/block_token.py
--- old/mistletoe-1.5.1/mistletoe/block_token.py        2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/block_token.py        2026-07-11 
16:09:13.000000000 +0200
@@ -59,7 +59,8 @@
     Arguments:
         token_cls (BlockToken): token to be removed from the parsing process.
     """
-    _token_types.remove(token_cls)
+    if token_cls in _token_types:
+        _token_types.remove(token_cls)
 
 
 def reset_tokens():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/contrib/github_wiki.py 
new/mistletoe-1.6.0/mistletoe/contrib/github_wiki.py
--- old/mistletoe-1.5.1/mistletoe/contrib/github_wiki.py        2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/contrib/github_wiki.py        2026-07-11 
16:09:13.000000000 +0200
@@ -11,7 +11,8 @@
 
 
 class GithubWiki(SpanToken):
-    pattern = re.compile(r"\[\[ *(.+?) *\| *(.+?) *\]\]")
+    # In order to avoid inefficient backtracking, we use positive lookahead 
here.
+    pattern = re.compile(r"\[\[(?=[^\n|]*\|[^\n]*\]\]) *([^\n|]+?) *\| 
*([^\n]+?) *\]\]")
 
     def __init__(self, match):
         self.target = match.group(2)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/contrib/jira_renderer.py 
new/mistletoe-1.6.0/mistletoe/contrib/jira_renderer.py
--- old/mistletoe-1.5.1/mistletoe/contrib/jira_renderer.py      2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/contrib/jira_renderer.py      2026-07-11 
16:09:13.000000000 +0200
@@ -23,7 +23,7 @@
 
 from itertools import chain
 from mistletoe import block_token, span_token
-from mistletoe.base_renderer import BaseRenderer
+from mistletoe.base_renderer import BaseRenderer, URI_SAFE_CHARACTERS
 import re
 
 
@@ -68,7 +68,7 @@
         inner = self.render_inner(token)
         target = escape_url(token.target)
         if token.title:
-            title = '|{}'.format(token.title)
+            title = '|{}'.format(escape_link_chars(token.title))
         else:
             title = ''
 
@@ -191,7 +191,7 @@
         else:
             template = '|{inner}'
 
-        inner = self.render_inner(token)
+        inner = self.render_inner(token).replace("|", "\\|")
         if inner == '':
             inner = ' '
         return template.format(inner=inner)
@@ -231,10 +231,21 @@
 
 def escape_url(raw):
     """
-    Escape urls to prevent code injection craziness. (Hopefully.)
+    Escapes the URL part of a Jira link.
     """
     from urllib.parse import quote
-    return quote(raw, safe='/#:()*?=%@+,&;')
+    return escape_link_chars(quote(raw, safe=URI_SAFE_CHARACTERS))
+
+
+def escape_link_chars(s: str) -> str:
+    """
+    Escapes special characters that are used in the Jira link syntax.
+    """
+    s = s.replace("[", "\\[")
+    s = s.replace("]", "\\]")
+    # Note: Escaping pipe doesn't seem to have an effect in the tested Jira 
version.
+    s = s.replace("|", "\\|")
+    return s
 
 
 JIRARenderer = JiraRenderer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mistletoe-1.5.1/mistletoe/contrib/xwiki20_renderer.py 
new/mistletoe-1.6.0/mistletoe/contrib/xwiki20_renderer.py
--- old/mistletoe-1.5.1/mistletoe/contrib/xwiki20_renderer.py   2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/contrib/xwiki20_renderer.py   2026-07-11 
16:09:13.000000000 +0200
@@ -1,6 +1,6 @@
 from itertools import chain
 from mistletoe import block_token, span_token
-from mistletoe.base_renderer import BaseRenderer
+from mistletoe.base_renderer import BaseRenderer, URI_SAFE_CHARACTERS
 
 
 class XWiki20Renderer(BaseRenderer):
@@ -234,4 +234,4 @@
     Escape urls to prevent code injection craziness. (Hopefully.)
     """
     from urllib.parse import quote
-    return quote(raw, safe='/#:()*?=%@+,&;')
+    return quote(raw, safe=URI_SAFE_CHARACTERS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/core_tokens.py 
new/mistletoe-1.6.0/mistletoe/core_tokens.py
--- old/mistletoe-1.5.1/mistletoe/core_tokens.py        2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/core_tokens.py        2026-07-11 
16:09:13.000000000 +0200
@@ -439,9 +439,10 @@
             self.number = self.end - self.start
             self.type = self.type[n:]
             return True
+        # Trim closing markers from the right so type matches start/end.
         self.end = self.end - n
         self.number = self.end - self.start
-        self.type = self.type[:n]
+        self.type = self.type[:-n]
         return True
 
     def closed_by(self, other):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/html_renderer.py 
new/mistletoe-1.6.0/mistletoe/html_renderer.py
--- old/mistletoe-1.5.1/mistletoe/html_renderer.py      2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/html_renderer.py      2026-07-11 
16:09:13.000000000 +0200
@@ -10,6 +10,7 @@
 from mistletoe.block_token import HtmlBlock
 from mistletoe.span_token import HtmlSpan
 from mistletoe.base_renderer import BaseRenderer
+from mistletoe.base_renderer import URI_SAFE_CHARACTERS
 
 
 class HtmlRenderer(BaseRenderer):
@@ -73,11 +74,12 @@
 
     def render_image(self, token: span_token.Image) -> str:
         template = '<img src="{}" alt="{}"{} />'
+        src = self.escape_url(token.src)
         if token.title:
             title = ' title="{}"'.format(html.escape(token.title))
         else:
             title = ''
-        return template.format(token.src, self.render_to_plain(token), title)
+        return template.format(src, self.render_to_plain(token), title)
 
     def render_link(self, token: span_token.Link) -> str:
         template = '<a href="{target}"{title}>{inner}</a>'
@@ -92,7 +94,7 @@
     def render_auto_link(self, token: span_token.AutoLink) -> str:
         template = '<a href="{target}">{inner}</a>'
         if token.mailto:
-            target = 'mailto:{}'.format(token.target)
+            target = self.escape_url('mailto:{}'.format(token.target))
         else:
             target = self.escape_url(token.target)
         inner = self.render_inner(token)
@@ -235,7 +237,7 @@
         """
         Escape urls to prevent code injection craziness. (Hopefully.)
         """
-        return html.escape(quote(raw, safe='/#:()*?=%@+,&;'))
+        return html.escape(quote(raw, safe=URI_SAFE_CHARACTERS))
 
 
 HTMLRenderer = HtmlRenderer
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/latex_renderer.py 
new/mistletoe-1.6.0/mistletoe/latex_renderer.py
--- old/mistletoe-1.5.1/mistletoe/latex_renderer.py     2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/latex_renderer.py     2026-07-11 
16:09:13.000000000 +0200
@@ -6,7 +6,7 @@
 from itertools import chain
 from urllib.parse import quote
 import mistletoe.latex_token as latex_token
-from mistletoe.base_renderer import BaseRenderer
+from mistletoe.base_renderer import BaseRenderer, URI_SAFE_CHARACTERS
 
 # (customizable) delimiters for inline code
 verb_delimiters = string.punctuation + string.digits
@@ -184,16 +184,12 @@
         """
         Quote unsafe chars in urls & escape as needed for LaTeX's hyperref.
 
-        %-escapes all characters that are neither in the unreserved chars
-        ("always safe" as per RFC 2396 or RFC 3986) nor in the chars set
-        '/#:()*?=%@+,&;'
-
-        Subsequently, LaTeX-escapes '%' and '#' for hyperref's \\url{} to also
+        LaTeX-escapes '%' and '#' for hyperref's \\url{} to also
         work if used within macros like \\multicolumn. if \\url{} with urls
         containing '%' or '#' is used outside of multicolumn-macros, they work
         regardless of whether these characters are escaped, and the result
         remains the same (at least for pdflatex from TeX Live 2019).
         """
-        quoted_url = quote(raw, safe='/#:()*?=%@+,&;')
+        quoted_url = quote(raw, safe=URI_SAFE_CHARACTERS)
         return quoted_url.replace('%', '\\%') \
                          .replace('#', '\\#')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/markdown_renderer.py 
new/mistletoe-1.6.0/mistletoe/markdown_renderer.py
--- old/mistletoe-1.5.1/mistletoe/markdown_renderer.py  2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/mistletoe/markdown_renderer.py  2026-07-11 
16:09:13.000000000 +0200
@@ -4,7 +4,7 @@
 
 import re
 from itertools import chain
-from typing import Iterable, Sequence
+from typing import Iterable, Optional, Sequence
 
 from mistletoe import block_token, span_token, token
 from mistletoe.base_renderer import BaseRenderer
@@ -95,8 +95,8 @@
     def __init__(
         self,
         *extras,
-        max_line_length: int = None,
-        normalize_whitespace=False
+        max_line_length: Optional[int] = None,
+        normalize_whitespace: bool = False,
     ):
         """
         Args:
@@ -254,12 +254,12 @@
     # rendered into sequences of lines (strings), to be joined by newlines.
 
     def render_document(
-        self, token: block_token.Document, max_line_length: int
+        self, token: block_token.Document, max_line_length: Optional[int]
     ) -> Iterable[str]:
         return self.blocks_to_lines(token.children, 
max_line_length=max_line_length)
 
     def render_heading(
-        self, token: block_token.Heading, max_line_length: int
+        self, token: block_token.Heading, max_line_length: Optional[int]
     ) -> Iterable[str]:
         # note: no word wrapping, because atx headings always fit on a single 
line.
         line = "#" * token.level
@@ -271,13 +271,13 @@
         return [line]
 
     def render_setext_heading(
-        self, token: block_token.SetextHeading, max_line_length: int
+        self, token: block_token.SetextHeading, max_line_length: Optional[int]
     ) -> Iterable[str]:
         yield from self.span_to_lines(token.children, 
max_line_length=max_line_length)
         yield token.underline
 
     def render_quote(
-        self, token: block_token.Quote, max_line_length: int
+        self, token: block_token.Quote, max_line_length: Optional[int]
     ) -> Iterable[str]:
         max_child_line_length = max_line_length - 2 if max_line_length else 
None
         lines = self.blocks_to_lines(
@@ -286,18 +286,18 @@
         return self.prefix_lines(lines or [""], "> ")
 
     def render_paragraph(
-        self, token: block_token.Paragraph, max_line_length: int
+        self, token: block_token.Paragraph, max_line_length: Optional[int]
     ) -> Iterable[str]:
         return self.span_to_lines(token.children, 
max_line_length=max_line_length)
 
     def render_block_code(
-        self, token: block_token.BlockCode, max_line_length: int
+        self, token: block_token.BlockCode, max_line_length: Optional[int]
     ) -> Iterable[str]:
         lines = token.content[:-1].split("\n")
         return self.prefix_lines(lines, "    ")
 
     def render_fenced_code_block(
-        self, token: block_token.BlockCode, max_line_length: int
+        self, token: block_token.CodeFence, max_line_length: Optional[int]
     ) -> Iterable[str]:
         indentation = " " * token.indentation
         yield indentation + token.delimiter + token.info_string
@@ -307,12 +307,12 @@
         yield indentation + token.delimiter
 
     def render_list(
-        self, token: block_token.List, max_line_length: int
+        self, token: block_token.List, max_line_length: Optional[int]
     ) -> Iterable[str]:
         return self.blocks_to_lines(token.children, 
max_line_length=max_line_length)
 
     def render_list_item(
-        self, token: block_token.ListItem, max_line_length: int
+        self, token: block_token.ListItem, max_line_length: Optional[int]
     ) -> Iterable[str]:
         if self.normalize_whitespace:
             prepend = len(token.leader) + 1
@@ -333,7 +333,7 @@
         )
 
     def render_table(
-        self, token: block_token.Table, max_line_length: int
+        self, token: block_token.Table, max_line_length: Optional[int]
     ) -> Iterable[str]:
         # note: column widths are not preserved; they are automatically 
adjusted to fit the contents.
         content = [self.table_row_to_text(token.header), []]
@@ -346,24 +346,24 @@
         ]
 
     def render_thematic_break(
-        self, token: block_token.ThematicBreak, max_line_length: int
+        self, token: block_token.ThematicBreak, max_line_length: Optional[int]
     ) -> Iterable[str]:
         return [token.line]
 
     def render_html_block(
-        self, token: block_token.HtmlBlock, max_line_length: int
+        self, token: block_token.HtmlBlock, max_line_length: Optional[int]
     ) -> Iterable[str]:
         return token.content.split("\n")
 
     def render_link_reference_definition_block(
-        self, token: LinkReferenceDefinitionBlock, max_line_length: int
+        self, token: LinkReferenceDefinitionBlock, max_line_length: 
Optional[int]
     ) -> Iterable[str]:
         # each link reference definition starts on a new line
         for child in token.children:
             yield from self.span_to_lines([child], 
max_line_length=max_line_length)
 
     def render_blank_line(
-        self, token: BlankLine, max_line_length: int
+        self, token: BlankLine, max_line_length: Optional[int]
     ) -> Iterable[str]:
         return [""]
 
@@ -373,7 +373,7 @@
         self,
         leader: Fragment,
         tokens: Iterable[span_token.SpanToken],
-        trailer: Fragment = None,
+        trailer: Optional[Fragment] = None,
     ) -> Iterable[Fragment]:
         """
         Makes fragments from `tokens` and embeds within a leader and a trailer.
@@ -384,7 +384,7 @@
         yield trailer or leader
 
     def blocks_to_lines(
-        self, tokens: Iterable[block_token.BlockToken], max_line_length: int
+        self, tokens: Iterable[block_token.BlockToken], max_line_length: 
Optional[int]
     ) -> Iterable[str]:
         """
         Renders a sequence of block tokens into a sequence of lines.
@@ -395,7 +395,7 @@
             )
 
     def span_to_lines(
-        self, tokens: Iterable[span_token.SpanToken], max_line_length: int
+        self, tokens: Iterable[span_token.SpanToken], max_line_length: 
Optional[int]
     ) -> Iterable[str]:
         """
         Renders a sequence of span (inline) tokens into a sequence of lines.
@@ -408,13 +408,24 @@
         """
         Renders a sequence of span (inline) tokens into a sequence of 
Fragments.
         """
-        return chain.from_iterable(
-            [self.render_map[token.__class__.__name__](token) for token in 
tokens]
-        )
+        token_list = list(tokens)
+        for i, token in enumerate(token_list):
+            if (
+                isinstance(token, span_token.LineBreak)
+                and token.soft
+                and (
+                    (i + 1 < len(token_list) and isinstance(token_list[i + 1], 
span_token.HtmlSpan))
+                    or (i > 0 and isinstance(token_list[i - 1], 
span_token.HtmlSpan))
+                )
+            ):
+                yield Fragment("\n", hard_line_break=True)
+            else:
+                yield from self.render_map[token.__class__.__name__](token)
+
 
     @classmethod
     def fragments_to_lines(
-        cls, fragments: Iterable[Fragment], max_line_length: int = None
+        cls, fragments: Iterable[Fragment], max_line_length: Optional[int] = 
None
     ) -> Iterable[str]:
         """
         Renders a sequence of Fragments into lines.
@@ -492,7 +503,7 @@
         cls,
         lines: Iterable[str],
         first_line_prefix: str,
-        following_line_prefix: str = None,
+        following_line_prefix: Optional[str] = None,
     ) -> Iterable[str]:
         """
         Prepends a prefix string to a sequence of lines. The first line may
@@ -512,7 +523,19 @@
         """
         Renders each table cell on a table row to text. No word wrapping.
         """
-        return [next(self.span_to_lines(col.children, max_line_length=None), 
"") for col in row.children]
+        return [
+            self.escape_table_cell_text(
+                next(self.span_to_lines(col.children, max_line_length=None), 
"")
+            )
+            for col in row.children
+        ]
+
+    @staticmethod
+    def escape_table_cell_text(text: str) -> str:
+        """
+        Escapes text that would otherwise be parsed as a table delimiter.
+        """
+        return text.replace("|", r"\|")
 
     @classmethod
     def calculate_table_column_widths(cls, col_text) -> Sequence[int]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/mistletoe/span_token.py 
new/mistletoe-1.6.0/mistletoe/span_token.py
--- old/mistletoe-1.5.1/mistletoe/span_token.py 2025-12-07 17:12:47.000000000 
+0100
+++ new/mistletoe-1.6.0/mistletoe/span_token.py 2026-07-11 16:09:13.000000000 
+0200
@@ -50,7 +50,8 @@
     Arguments:
         token_cls (SpanToken): token to be removed from the parsing process.
     """
-    _token_types.remove(token_cls)
+    if token_cls in _token_types:
+        _token_types.remove(token_cls)
 
 
 def reset_tokens():
@@ -192,7 +193,7 @@
 
 class AutoLink(SpanToken):
     """
-    Autolink token. ("<http://www.google.com>")
+    Autolink token. ("<http://www.google.com>", or "<[email protected]>")
     This is an inline token with a single child of type RawText.
 
     Attributes:
@@ -201,14 +202,15 @@
         mailto (bool): true iff the target looks like an email address, but 
does not have the "mailto:"; prefix.
     """
     repr_attributes = ("target", "mailto")
-    pattern = re.compile(r"(?<!\\)(?:\\\\)*<([A-Za-z][A-Za-z0-9+.-]{1,31}:[^ 
<>]*?|[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*)>")
+    pattern = re.compile(r"(?<!\\)(?:\\\\)*<(([A-Za-z][A-Za-z0-9+.-]{1,31}):[^ 
<>]*?|[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*)>")
     parse_inner = False
 
     def __init__(self, match):
         content = match.group(self.parse_group)
+        scheme = match.group(2)
         self.children = (RawText(content),)
         self.target = content
-        self.mailto = '@' in self.target and 'mailto' not in 
self.target.casefold()
+        self.mailto = not scheme
 
 
 class EscapeSequence(SpanToken):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/setup.py new/mistletoe-1.6.0/setup.py
--- old/mistletoe-1.5.1/setup.py        2025-12-07 17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/setup.py        2026-07-11 16:09:13.000000000 +0200
@@ -29,6 +29,9 @@
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
         'Programming Language :: Python :: 3.11',
+        'Programming Language :: Python :: 3.12',
+        'Programming Language :: Python :: 3.13',
+        'Programming Language :: Python :: 3.14',
         'Programming Language :: Python :: Implementation :: CPython',
         'Programming Language :: Python :: Implementation :: PyPy',
         'Topic :: Software Development :: Libraries :: Python Modules',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/test/specification/commonmark.py 
new/mistletoe-1.6.0/test/specification/commonmark.py
--- old/mistletoe-1.5.1/test/specification/commonmark.py        2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/test/specification/commonmark.py        2026-07-11 
16:09:13.000000000 +0200
@@ -49,7 +49,15 @@
 
 def load_tests(specfile):
     with open(specfile, 'r', encoding='utf-8') as fin:
-        return json.load(fin)
+        tests = json.load(fin)
+
+    # alter the tests for our needs
+    for test in tests:
+        # CommonMark states that "Renderers may make different decisions about 
how to escape or normalize URLs in the output.".
+        # So we unescape square brackets in URLs to match mistletoe's behavior.
+        test['html'] = test['html'].replace('%5B', '[').replace('%5D', ']')
+
+    return tests
 
 
 def locate_section(section, tests):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mistletoe-1.5.1/test/test_contrib/test_github_wiki.py 
new/mistletoe-1.6.0/test/test_contrib/test_github_wiki.py
--- old/mistletoe-1.5.1/test/test_contrib/test_github_wiki.py   2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/test/test_contrib/test_github_wiki.py   2026-07-11 
16:09:13.000000000 +0200
@@ -1,4 +1,6 @@
+import time
 from unittest import TestCase, mock
+from parameterized import parameterized
 from mistletoe import span_token, Document, token
 from mistletoe.span_token import tokenize_inner
 from mistletoe.contrib.github_wiki import GithubWiki, GithubWikiRenderer
@@ -28,3 +30,25 @@
         token = next(iter(tokenize_inner('[[wiki|target]]')))
         output = '<a href="target">wiki</a>'
         self.assertEqual(self.renderer.render(token), output)
+
+    @parameterized.expand([
+        ('[[wiki | target]]', 'wiki', 'target'),
+        ('[[ wiki | target ]]', 'wiki', 'target'),
+        ('[[a | b | c]]', 'a', 'b | c'),
+    ])
+    def test_parse_variant_spacing_and_pipes(self, source, label, target):
+        token = next(iter(tokenize_inner(source)))
+        self.assertIsInstance(token, GithubWiki)
+        self.assertEqual(token.target, target)
+        self.assertEqual(self.renderer.render_inner(token), label)
+
+    @parameterized.expand([
+        ('missing_closing_marker', '[[' + ' ' * 200 + '|' + ' ' * 200 + ']'),
+        ('missing_pipe', '[[' + ' ' * 6000 + ']]'),
+    ])
+    def test_malformed_input_is_rejected_quickly(self, _name, source):
+        start = time.perf_counter()
+        match = GithubWiki.pattern.match(source)
+        elapsed = time.perf_counter() - start
+        self.assertLess(elapsed, 1.0)
+        self.assertIsNone(match)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mistletoe-1.5.1/test/test_contrib/test_jira_renderer.py 
new/mistletoe-1.6.0/test/test_contrib/test_jira_renderer.py
--- old/mistletoe-1.5.1/test/test_contrib/test_jira_renderer.py 2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/test/test_contrib/test_jira_renderer.py 2026-07-11 
16:09:13.000000000 +0200
@@ -113,6 +113,12 @@
         expected = '[{body}|{url}|{title}]'.format(url=url, body=body, 
title=title)
         self.assertEqual(output, expected)
 
+    def test_render_link_with_escapes(self):
+        token = next(iter(tokenize_inner('[A link with 
\\[brackets\\]](http://wwww.example.com/some[complex]url "[A title]")')))
+        output = self.renderer.render(token)
+        expected = '[A link with 
\\[brackets\\]|http://wwww.example.com/some\\[complex\\]url|\\[A title\\]]'
+        self.assertEqual(output, expected)
+
     def test_render_footnote_link(self):
         pass
 
@@ -211,6 +217,20 @@
 
 """
         self.markdownResultTest(markdown, expected)
+
+    def test_table_pipe_in_cell(self):
+        """ Escape pipe in cells """
+        markdown = """\
+| A     | B |
+|-----------|
+| 1\\|1 | 2 |
+"""
+        expected = """\
+||A||B||
+|1\\|1|2|
+
+"""
+        self.markdownResultTest(markdown, expected)
 
     @filesBasedTest
     def test_render__basic_blocks(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/test/test_core_tokens.py 
new/mistletoe-1.6.0/test/test_core_tokens.py
--- old/mistletoe-1.5.1/test/test_core_tokens.py        2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/test/test_core_tokens.py        2026-07-11 
16:09:13.000000000 +0200
@@ -30,6 +30,7 @@
         self.assertEqual(delimiter.number, 1)
         self.assertEqual(delimiter.start, 5)
         self.assertEqual(delimiter.end, 6)
+        self.assertEqual(delimiter.type, '*')
 
     def test_delimiter_remove_right(self):
         delimiter = Delimiter(4, 6, 'abcd**')
@@ -37,6 +38,15 @@
         self.assertEqual(delimiter.number, 1)
         self.assertEqual(delimiter.start, 4)
         self.assertEqual(delimiter.end, 5)
+        self.assertEqual(delimiter.type, '*')
+
+    def test_delimiter_remove_right_from_longer_run(self):
+        delimiter = Delimiter(4, 8, 'abcd****')
+        self.assertTrue(delimiter.remove(1, left=False))
+        self.assertEqual(delimiter.number, 3)
+        self.assertEqual(delimiter.start, 4)
+        self.assertEqual(delimiter.end, 7)
+        self.assertEqual(delimiter.type, '***')
 
     def test_delimiter_remove_empty(self):
         delimiter = Delimiter(4, 6, 'abcd**')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/test/test_html_renderer.py 
new/mistletoe-1.6.0/test/test_html_renderer.py
--- old/mistletoe-1.5.1/test/test_html_renderer.py      2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/test/test_html_renderer.py      2026-07-11 
16:09:13.000000000 +0200
@@ -40,8 +40,8 @@
         self._test_token('Strikethrough', '<del>inner</del>')
 
     def test_image(self):
-        expected = '<img src="src" alt="" title="title" />'
-        self._test_token('Image', expected, src='src', title='title')
+        expected = '<img src="s&amp;r%3Ec" alt="" title="title" />'
+        self._test_token('Image', expected, src='s&r>c', title='title')
 
     def test_link(self):
         expected = '<a href="target" title="title">inner</a>'
@@ -51,6 +51,11 @@
         expected = '<a href="link">inner</a>'
         self._test_token('AutoLink', expected, target='link', mailto=False)
 
+    def test_email_autolink_escapes_href(self):
+        expected = '<a href="mailto:a&amp;[email protected]";>inner</a>'
+        self._test_token('AutoLink', expected, target='a&[email protected]',
+                         mailto=True)
+
     def test_escape_sequence(self):
         self._test_token('EscapeSequence', 'inner')
 
@@ -158,6 +163,12 @@
             expected = '<p>&lt;div&gt;&lt;br&gt; as plain 
text&lt;/div&gt;</p>\n'
             self.assertEqual(renderer.render(token), expected)
 
+    def test_email_autolink_escapes_href(self):
+        with HtmlRenderer() as renderer:
+            output = renderer.render(Document('<a&[email protected]>'))
+        expected = '<p><a 
href="mailto:a&amp;[email protected]";>a&amp;[email protected]</a></p>\n'
+        self.assertEqual(output, expected)
+
 
 class TestHtmlRendererFootnotes(TestCase):
     def setUp(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/test/test_markdown_renderer.py 
new/mistletoe-1.6.0/test/test_markdown_renderer.py
--- old/mistletoe-1.5.1/test/test_markdown_renderer.py  2025-12-07 
17:12:47.000000000 +0100
+++ new/mistletoe-1.6.0/test/test_markdown_renderer.py  2026-07-11 
16:09:13.000000000 +0200
@@ -338,6 +338,15 @@
         output = self.roundtrip(input)
         self.assertEqual(output, "".join(input))
 
+    def test_table_escapes_pipes_in_cells(self):
+        input = [
+            "| Left column | Right column                  |\n",
+            "| ----------- | ----------------------------- |\n",
+            "| left baz    | right foo \\| between pipes \\| |\n",
+        ]
+        output = self.roundtrip(input)
+        self.assertEqual(output, "".join(input))
+
     def test_table_with_varying_column_counts(self):
         input = [
             "   |   header | x |  \n",
@@ -638,6 +647,28 @@
                 "> > hills of hay\n"
             )
 
+    def test_wordwrap_paragraph_with_html_span_on_own_line(self):
+        with MarkdownRenderer() as renderer:
+            paragraph = block_token.Paragraph(
+                [
+                    "Some long sentence that will be reflowed by the word 
wrapper.\n",
+                    "</template>\n",
+                    "More text after the tag.\n",
+                ]
+            )
+
+            renderer.max_line_length = 50
+            lines = renderer.render(paragraph)
+
+            # then the closing tag must remain on its own line — not collapsed
+            # onto the end of the previous line, and not merged with the next 
line
+            assert lines == (
+                "Some long sentence that will be reflowed by the\n"
+                "word wrapper.\n"
+                "</template>\n"
+                "More text after the tag.\n"
+            )
+
     def test_wordwrap_tables(self):
         with MarkdownRenderer(max_line_length=30) as renderer:
             # given a markdown table
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/test/test_repr.py 
new/mistletoe-1.6.0/test/test_repr.py
--- old/mistletoe-1.5.1/test/test_repr.py       2025-12-07 17:12:47.000000000 
+0100
+++ new/mistletoe-1.6.0/test/test_repr.py       2026-07-11 16:09:13.000000000 
+0200
@@ -103,6 +103,12 @@
         doc = Document("Foo <http://www.example.org/>\n")
         self._check_repr_matches(doc.children[0].children[1], 
"span_token.AutoLink with 1 child target='http://www.example.org/' 
mailto=False")
 
+        doc = Document("Foo <[email protected]>\n")
+        self._check_repr_matches(doc.children[0].children[1], 
"span_token.AutoLink with 1 child target='[email protected]' mailto=True")
+
+        doc = Document("Foo <http://example.org/@user/1234>\n")
+        self._check_repr_matches(doc.children[0].children[1], 
"span_token.AutoLink with 1 child target='http://example.org/@user/1234' 
mailto=False")
+
     def test_escapesequence(self):
         doc = Document("\\*\n")
         self._check_repr_matches(doc.children[0].children[0], 
"span_token.EscapeSequence with 1 child")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mistletoe-1.5.1/test/test_span_token.py 
new/mistletoe-1.6.0/test/test_span_token.py
--- old/mistletoe-1.5.1/test/test_span_token.py 2025-12-07 17:12:47.000000000 
+0100
+++ new/mistletoe-1.6.0/test/test_span_token.py 2026-07-11 16:09:13.000000000 
+0200
@@ -35,6 +35,24 @@
         self._test_token(next(tokens), 'bar', children=True)
         self._test_token(next(tokens), '***baz', children=False)
 
+    def test_overlapping_delimiter_runs_do_not_crash(self):
+        tokens = list(span_token.tokenize_inner('**a****a*'))
+        self.assertEqual(len(tokens), 3)
+        self.assertIsInstance(tokens[0], span_token.Strong)
+        self.assertIsInstance(tokens[2], span_token.Emphasis)
+        self.mock.assert_any_call('*')
+
+    def test_issue_261_asterisk_sequences_do_not_crash(self):
+        cases = [
+            '**IKXZXPTB****AJFHV *Pbjovp*VSN*',
+            '**IKXZXPTB****AJFHV*Pbjovp*',
+            'Ch******her R**a, Bu*****ly In****nt, j**n ko***ob, '
+            'ji*yu, M**s, Ch****us, M*',
+        ]
+        for case in cases:
+            with self.subTest(case=case):
+                self.assertTrue(list(span_token.tokenize_inner(case)))
+
 
 class TestEmphasis(TestBranchToken):
     def test_parse(self):

Reply via email to