diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0b652494f..1b8b661f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -126,6 +126,28 @@ jobs: PIP_CONSTRAINT= hatch env run -e test -- pip install 'pip>=24.2' xvfb-run --auto-servernum hatch run test:nowarn || xvfb-run --auto-servernum hatch run test:nowarn --lf + test_mistune_30: + name: Test Mistune 3.0 + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install texlive-plain-generic inkscape texlive-xetex latexmk + sudo apt-get install xvfb x11-utils libxkbcommon-x11-0 libxcb-xinerama0 python3-pyqt5 + + # pandoc is not up to date in the ubuntu repos, so we install directly + wget https://github.com/jgm/pandoc/releases/download/2.9.2.1/pandoc-2.9.2.1-1-amd64.deb && sudo dpkg -i pandoc-2.9.2.1-1-amd64.deb + + - name: Run tests + run: | + hatch env run -e test -- pip install 'mistune~=3.0.0' + xvfb-run --auto-servernum hatch run test:nowarn || xvfb-run --auto-servernum hatch run test:nowarn --lf + test_prereleases: name: Test Prereleases runs-on: ubuntu-latest diff --git a/docs/source/nbconvert_library.ipynb b/docs/source/nbconvert_library.ipynb index dc9ea0e1e..7757235ad 100644 --- a/docs/source/nbconvert_library.ipynb +++ b/docs/source/nbconvert_library.ipynb @@ -486,7 +486,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "@damianavila wrote the Nikola Plugin to [write blog post as Notebooks](http://damianavila.github.io/blog/posts/one-line-deployment-of-your-site-to-gh-pages.html) and is developing a js-extension to publish notebooks via one click from the web app." + "@damianavila wrote the Nikola Plugin to [write blog post as Notebooks](https://damianavila.github.io/blog/posts/one-line-deployment-of-your-site-to-gh-pages) and is developing a js-extension to publish notebooks via one click from the web app." ] } ], diff --git a/nbconvert/filters/markdown_mistune.py b/nbconvert/filters/markdown_mistune.py index 02ab346e0..fb8828167 100644 --- a/nbconvert/filters/markdown_mistune.py +++ b/nbconvert/filters/markdown_mistune.py @@ -9,7 +9,7 @@ import mimetypes import os from html import escape -from typing import Any, Callable, Dict, Iterable, Match, Optional, Tuple +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Iterable, Match, Optional, Protocol, Tuple import bs4 from pygments import highlight @@ -20,6 +20,19 @@ from nbconvert.filters.strings import add_anchor +if TYPE_CHECKING: + try: + from mistune.plugins import Plugin + except ImportError: + + class Plugin(Protocol): # type: ignore[no-redef] + """Mistune plugin interface.""" + + def __call__(self, markdown: "Markdown") -> None: + """Apply the plugin on the markdown document.""" + ... + + try: # for Mistune >= 3.0 from mistune import ( # type:ignore[attr-defined] BlockParser, @@ -32,6 +45,7 @@ ) MISTUNE_V3 = True + MISTUNE_V3_ATX = "atx_heading" in BlockParser.SPECIFICATION except ImportError: # for Mistune >= 2.0 import re @@ -45,8 +59,9 @@ ) MISTUNE_V3 = False + MISTUNE_V3_ATX = False - def import_plugin(name: str) -> "MarkdownPlugin": # type: ignore[misc] + def import_plugin(name: str) -> "Plugin": # type: ignore[misc] """Simple implementation of Mistune V3's import_plugin for V2.""" return PLUGINS[name] # type: ignore[no-any-return] @@ -75,8 +90,10 @@ class MathBlockParser(BlockParser): is ignored here. """ - AXT_HEADING_WITHOUT_LEADING_SPACES = ( - r"^ {0,3}(?P#{1,6})(?!#+)(?P[ \t]*(.*?)?)$" + ATX_HEADING_WITHOUT_LEADING_SPACES = ( + r"^ {0,3}(?P#{1,6})(?!#+)(?P[ \t]*(.*?)?)$" + if MISTUNE_V3_ATX + else r"^ {0,3}(?P#{1,6})(?!#+)(?P[ \t]*(.*?)?)$" ) MULTILINE_MATH = _dotall( @@ -92,12 +109,14 @@ class MathBlockParser(BlockParser): SPECIFICATION = { **BlockParser.SPECIFICATION, - "axt_heading": AXT_HEADING_WITHOUT_LEADING_SPACES, + ( + "atx_heading" if MISTUNE_V3_ATX else "axt_heading" + ): ATX_HEADING_WITHOUT_LEADING_SPACES, "multiline_math": MULTILINE_MATH, } # Multiline math must be searched before other rules - DEFAULT_RULES: Tuple[str, ...] = ("multiline_math", *BlockParser.DEFAULT_RULES) # type: ignore[assignment] + DEFAULT_RULES: ClassVar[Iterable[str]] = ("multiline_math", *BlockParser.DEFAULT_RULES) # type: ignore[assignment] def parse_multiline_math(self, m: Match[str], state: BlockState) -> int: """Send mutiline math as a single paragraph to MathInlineParser.""" @@ -139,7 +158,7 @@ class MathInlineParser(InlineParser): } # Block math must be matched first, and all math must come before text - DEFAULT_RULES: Tuple[str, ...] = ( + DEFAULT_RULES: ClassVar[Iterable[str]] = ( "block_math_tex", "block_math_latex", "inline_math_tex", @@ -442,10 +461,6 @@ def _html_embed_images(self, html: str) -> str: return str(parsed_html) -# Represents an already imported plugin for Mistune -MarkdownPlugin = Callable[[Markdown], None] - - class MarkdownWithMath(Markdown): """Markdown text with math enabled.""" @@ -464,7 +479,7 @@ def __init__( renderer: HTMLRenderer, block: Optional[BlockParser] = None, inline: Optional[InlineParser] = None, - plugins: Optional[Iterable[MarkdownPlugin]] = None, + plugins: Optional[Iterable["Plugin"]] = None, ): """Initialize the parser.""" if block is None: