From 9904ae00f86c79936d20b561a5b5cd660bf40ab9 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Fri, 23 Feb 2024 13:51:34 +0100 Subject: [PATCH 1/3] feat: Support [`identifier`][] with pymdownx.inlinehilite enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Timothée Mazzucotelli --- pyproject.toml | 2 ++ src/mkdocs_autorefs/references.py | 5 ++++- tests/test_references.py | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index eec6329..691dd27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ classifiers = [ ] dependencies = [ "Markdown>=3.3", + "markupsafe>=2.0.1", "mkdocs>=1.1", ] @@ -90,6 +91,7 @@ tests = [ "pytest-cov>=4.1", "pytest-randomly>=3.15", "pytest-xdist>=3.3", + "pymdown-extensions>=10.0", ] typing = [ "mypy>=1.5", diff --git a/src/mkdocs_autorefs/references.py b/src/mkdocs_autorefs/references.py index 66b4931..78d9b82 100644 --- a/src/mkdocs_autorefs/references.py +++ b/src/mkdocs_autorefs/references.py @@ -8,9 +8,10 @@ from urllib.parse import urlsplit from xml.etree.ElementTree import Element +import markupsafe from markdown.extensions import Extension from markdown.inlinepatterns import REFERENCE_RE, ReferenceInlineProcessor -from markdown.util import INLINE_PLACEHOLDER_RE +from markdown.util import HTML_PLACEHOLDER_RE, INLINE_PLACEHOLDER_RE if TYPE_CHECKING: from markdown import Markdown @@ -88,6 +89,8 @@ def evalId(self, data: str, index: int, text: str) -> EvalIDType: # noqa: N802 # https://github.com/Python-Markdown/markdown/blob/1858c1b601ead62ed49646ae0d99298f41b1a271/markdown/inlinepatterns.py#L78 if INLINE_PLACEHOLDER_RE.fullmatch(identifier): identifier = self.unescape(identifier) + if match := HTML_PLACEHOLDER_RE.fullmatch(identifier): + identifier = markupsafe.Markup(self.md.htmlStash.rawHtmlBlocks[int(match.group(1))]).striptags() end = m.end(0) return identifier, end, True diff --git a/tests/test_references.py b/tests/test_references.py index 5a25844..37937fb 100644 --- a/tests/test_references.py +++ b/tests/test_references.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Mapping + import markdown import pytest @@ -44,6 +46,7 @@ def run_references_test( output: str, unmapped: list[str] | None = None, from_url: str = "page.html", + extensions: Mapping = {}, ) -> None: """Help running tests about references. @@ -54,7 +57,7 @@ def run_references_test( unmapped: The expected unmapped list. from_url: The source page URL. """ - md = markdown.Markdown(extensions=[AutorefsExtension()]) + md = markdown.Markdown(extensions=[AutorefsExtension(), *extensions], extension_configs=extensions) content = md.convert(source) def url_mapper(identifier: str) -> str: @@ -92,6 +95,26 @@ def test_reference_implicit_with_code() -> None: ) +def test_reference_implicit_with_code_inlinehilite_plain() -> None: + """Check implicit references (identifier in backticks, wrapped by inlinehilite).""" + run_references_test( + extensions={"pymdownx.inlinehilite": {}}, + url_map={"pathlib.Path": "pathlib.html#Path"}, + source="This [`pathlib.Path`][].", + output='

This pathlib.Path.

', + ) + + +def test_reference_implicit_with_code_inlinehilite_python() -> None: + """Check implicit references (identifier in backticks, syntax-highlighted by inlinehilite).""" + run_references_test( + extensions={"pymdownx.inlinehilite": {"style_plain_text": "python"}}, + url_map={"pathlib.Path": "pathlib.html#Path"}, + source="This [`pathlib.Path`][].", + output='

This pathlib.Path.

', + ) + + def test_reference_with_punctuation() -> None: """Check references with punctuation.""" run_references_test( From 1ecd2a36d819f81bde16c56364f7f06dd996e4ae Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Fri, 23 Feb 2024 14:09:45 +0100 Subject: [PATCH 2/3] fixup! feat: Support [`identifier`][] with pymdownx.inlinehilite enabled --- pyproject.toml | 3 ++- tests/test_references.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 691dd27..d341542 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,11 +87,12 @@ quality = [ "ruff>=0.0", ] tests = [ + "pygments>=2.16", + "pymdown-extensions>=10.0", "pytest>=7.4", "pytest-cov>=4.1", "pytest-randomly>=3.15", "pytest-xdist>=3.3", - "pymdown-extensions>=10.0", ] typing = [ "mypy>=1.5", diff --git a/tests/test_references.py b/tests/test_references.py index 37937fb..9b29ede 100644 --- a/tests/test_references.py +++ b/tests/test_references.py @@ -108,7 +108,7 @@ def test_reference_implicit_with_code_inlinehilite_plain() -> None: def test_reference_implicit_with_code_inlinehilite_python() -> None: """Check implicit references (identifier in backticks, syntax-highlighted by inlinehilite).""" run_references_test( - extensions={"pymdownx.inlinehilite": {"style_plain_text": "python"}}, + extensions={"pymdownx.inlinehilite": {"style_plain_text": "python"}, "pymdownx.highlight": {}}, url_map={"pathlib.Path": "pathlib.html#Path"}, source="This [`pathlib.Path`][].", output='

This pathlib.Path.

', From 15ace7c0d9cb618e4c98184fe9fd8128e7f5cba6 Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Fri, 23 Feb 2024 15:50:30 +0100 Subject: [PATCH 3/3] fixup! fixup! feat: Support [`identifier`][] with pymdownx.inlinehilite enabled --- src/mkdocs_autorefs/references.py | 29 +++++++++++++++++------------ tests/test_references.py | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/mkdocs_autorefs/references.py b/src/mkdocs_autorefs/references.py index 78d9b82..a4d2ab6 100644 --- a/src/mkdocs_autorefs/references.py +++ b/src/mkdocs_autorefs/references.py @@ -4,7 +4,7 @@ import re from html import escape, unescape -from typing import TYPE_CHECKING, Any, Callable, Match, Tuple +from typing import TYPE_CHECKING, Any, Callable, Match from urllib.parse import urlsplit from xml.etree.ElementTree import Element @@ -25,8 +25,6 @@ in the [`on_post_page` hook][mkdocs_autorefs.plugin.AutorefsPlugin.on_post_page]. """ -EvalIDType = Tuple[Any, Any, Any] - class AutoRefInlineProcessor(ReferenceInlineProcessor): """A Markdown extension.""" @@ -37,7 +35,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107 # Code based on # https://github.com/Python-Markdown/markdown/blob/8e7528fa5c98bf4652deb13206d6e6241d61630b/markdown/inlinepatterns.py#L780 - def handleMatch(self, m: Match[str], data: Any) -> Element | EvalIDType: # type: ignore[override] # noqa: N802 + def handleMatch(self, m: Match[str], data: str) -> tuple[Element | None, int | None, int | None]: # type: ignore[override] # noqa: N802 """Handle an element that matched. Arguments: @@ -52,7 +50,7 @@ def handleMatch(self, m: Match[str], data: Any) -> Element | EvalIDType: # type return None, None, None identifier, end, handled = self.evalId(data, index, text) - if not handled: + if not handled or identifier is None: return None, None, None if re.search(r"[/ \x00-\x1f]", identifier): @@ -62,9 +60,9 @@ def handleMatch(self, m: Match[str], data: Any) -> Element | EvalIDType: # type # but references with Markdown formatting are not possible anyway. return None, m.start(0), end - return self.makeTag(identifier, text), m.start(0), end + return self._make_tag(identifier, text), m.start(0), end - def evalId(self, data: str, index: int, text: str) -> EvalIDType: # noqa: N802 (parent's casing) + def evalId(self, data: str, index: int, text: str) -> tuple[str | None, int, bool]: # noqa: N802 (parent's casing) """Evaluate the id portion of `[ref][id]`. If `[ref][]` use `[ref]`. @@ -87,15 +85,22 @@ def evalId(self, data: str, index: int, text: str) -> EvalIDType: # noqa: N802 # Allow the entire content to be one placeholder, with the intent of catching things like [`Foo`][]. # It doesn't catch [*Foo*][] though, just due to the priority order. # https://github.com/Python-Markdown/markdown/blob/1858c1b601ead62ed49646ae0d99298f41b1a271/markdown/inlinepatterns.py#L78 - if INLINE_PLACEHOLDER_RE.fullmatch(identifier): - identifier = self.unescape(identifier) - if match := HTML_PLACEHOLDER_RE.fullmatch(identifier): - identifier = markupsafe.Markup(self.md.htmlStash.rawHtmlBlocks[int(match.group(1))]).striptags() + if match := INLINE_PLACEHOLDER_RE.fullmatch(identifier): + stashed_nodes: dict[str, Element | str] = self.md.treeprocessors["inline"].stashed_nodes # type: ignore[attr-defined] + el = stashed_nodes.get(match[1]) + if isinstance(el, Element) and el.tag == "code": + identifier = "".join(el.itertext()) + # Special case: allow pymdownx.inlinehilite raw snippets but strip them back to unhighlighted. + if match := HTML_PLACEHOLDER_RE.fullmatch(identifier): + stash_index = int(match.group(1)) + html = self.md.htmlStash.rawHtmlBlocks[stash_index] + identifier = markupsafe.Markup(html).striptags() + self.md.htmlStash.rawHtmlBlocks[stash_index] = escape(identifier) end = m.end(0) return identifier, end, True - def makeTag(self, identifier: str, text: str) -> Element: # type: ignore[override] # noqa: N802 + def _make_tag(self, identifier: str, text: str) -> Element: """Create a tag that can be matched by `AUTO_REF_RE`. Arguments: diff --git a/tests/test_references.py b/tests/test_references.py index 9b29ede..02b3f50 100644 --- a/tests/test_references.py +++ b/tests/test_references.py @@ -111,7 +111,7 @@ def test_reference_implicit_with_code_inlinehilite_python() -> None: extensions={"pymdownx.inlinehilite": {"style_plain_text": "python"}, "pymdownx.highlight": {}}, url_map={"pathlib.Path": "pathlib.html#Path"}, source="This [`pathlib.Path`][].", - output='

This pathlib.Path.

', + output='

This pathlib.Path.

', )