Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-nbconvert for openSUSE:Factory checked in at 2023-06-21 22:39:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-nbconvert (Old) and /work/SRC/openSUSE:Factory/.python-nbconvert.new.15902 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-nbconvert" Wed Jun 21 22:39:16 2023 rev:24 rq:1094160 version:7.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-nbconvert/python-nbconvert.changes 2023-06-12 15:27:36.995465594 +0200 +++ /work/SRC/openSUSE:Factory/.python-nbconvert.new.15902/python-nbconvert.changes 2023-06-21 22:40:14.990512243 +0200 @@ -1,0 +2,11 @@ +Tue Jun 20 18:19:24 UTC 2023 - Ben Greiner <c...@bnavigator.de> + +- Update to 7.6.0 + * Update to Mistune v3 #1820 (@TiagodePAlves) +- Release 7.5.0 + * Add mermaidjs 10.2.3 #1957 (@bollwyvl) + * Fix pdf conversion with explicitly relative paths #2005 + (@tuncbkose) + * Ensure TEXINPUTS is an absolute path #2002 (@tuncbkose) + +------------------------------------------------------------------- Old: ---- nbconvert-7.4.0.tar.gz New: ---- nbconvert-7.6.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-nbconvert.spec ++++++ --- /var/tmp/diff_new_pack.VxTfC2/_old 2023-06-21 22:40:15.554515637 +0200 +++ /var/tmp/diff_new_pack.VxTfC2/_new 2023-06-21 22:40:15.562515685 +0200 @@ -29,12 +29,11 @@ %else %bcond_with libalternatives %endif -# avoid rewriting -%define python3dist python3dist -# 7.4.0 gets abbreviated by pythondistdeps -%define shortversion 7.4 + +# 7.6.0 gets abbreviated by pythondistdeps +%define shortversion 7.6 Name: python-nbconvert%{psuffix} -Version: 7.4.0 +Version: 7.6.0 Release: 0 Summary: Conversion of Jupyter Notebooks License: BSD-3-Clause AND MIT @@ -51,17 +50,17 @@ Requires: python-MarkupSafe >= 2.0 Requires: python-Pygments >= 2.4.1 Requires: python-beautifulsoup4 -Requires: python-bleach Requires: python-defusedxml Requires: python-jupyter-core >= 4.7 Requires: python-jupyterlab-pygments Requires: python-nbclient >= 0.5 -Requires: python-nbformat >= 5.1 +Requires: python-nbformat >= 5.7 Requires: python-packaging Requires: python-pandocfilters >= 1.4.1 Requires: python-tinycss2 -Requires: python-traitlets >= 5.0 -Requires: (python-mistune >= 2.0.3 with python-mistune < 3) +Requires: python-traitlets >= 5.1 +Requires: (python-bleach without python-bleach = 5.0.0) +Requires: (python-mistune >= 2.0.3 with python-mistune < 4) Recommends: pandoc Recommends: python-tornado >= 6.1 Suggests: %{name}-latex @@ -97,7 +96,7 @@ Summary: Conversion of Jupyter Notebooks Requires: jupyter-ipykernel Requires: jupyter-jupyter-core -Requires: %python3dist(nbconvert) = %{shortversion} +Requires: python3dist(nbconvert) = %{shortversion} Conflicts: python3-jupyter_nbconvert < 5.5.0 %description -n jupyter-nbconvert ++++++ nbconvert-7.4.0.tar.gz -> nbconvert-7.6.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/.github/workflows/tests.yml new/nbconvert-7.6.0/.github/workflows/tests.yml --- old/nbconvert-7.4.0/.github/workflows/tests.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/.github/workflows/tests.yml 2020-02-02 01:00:00.000000000 +0100 @@ -42,7 +42,7 @@ 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.14.2/pandoc-2.14.2-1-amd64.deb && sudo dpkg -i pandoc-2.14.2-1-amd64.deb + wget https://github.com/jgm/pandoc/releases/download/3.1.2/pandoc-3.1.2-1-amd64.deb && sudo dpkg -i pandoc-3.1.2-1-amd64.deb - name: Run tests on Linux if: ${{ startsWith(runner.os, 'linux') }} @@ -110,10 +110,18 @@ with: dependency_type: minimum only_create_file: 1 - - name: Run the unit tests + - 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.14.2/pandoc-2.14.2-1-amd64.deb && sudo dpkg -i pandoc-2.14.2-1-amd64.deb + + - name: Run tests run: | - export NBFORMAT_VALIDATOR=jsonschema - hatch run test:nowarn || hatch run test:nowarn --lf + xvfb-run --auto-servernum hatch run test:nowarn || xvfb-run --auto-servernum hatch run test:nowarn --lf test_prereleases: name: Test Prereleases diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/.pre-commit-config.yaml new/nbconvert-7.6.0/.pre-commit-config.yaml --- old/nbconvert-7.4.0/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -19,7 +19,7 @@ - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.22.0 + rev: 0.23.1 hooks: - id: check-github-workflows @@ -36,7 +36,7 @@ - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.263 + rev: v0.0.270 hooks: - id: ruff args: ["--fix"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/CHANGELOG.md new/nbconvert-7.6.0/CHANGELOG.md --- old/nbconvert-7.4.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 @@ -2,6 +2,46 @@ <!-- <START NEW CHANGELOG ENTRY> --> +## 7.6.0 + +([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.5.0...60af6d897c083444586829c636f278d84ae81962)) + +### Maintenance and upkeep improvements + +- Update to Mistune v3 [#1820](https://github.com/jupyter/nbconvert/pull/1820) ([@TiagodePAlves](https://github.com/TiagodePAlves)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/nbconvert/graphs/contributors?from=2023-06-13&to=2023-06-19&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Ablink1073+updated%3A2023-06-13..2023-06-19&type=Issues) | [@kloczek](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Akloczek+updated%3A2023-06-13..2023-06-19&type=Issues) | [@TiagodePAlves](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3ATiagodePAlves+updated%3A2023-06-13..2023-06-19&type=Issues) + +<!-- <END NEW CHANGELOG ENTRY> --> + +## 7.5.0 + +([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.4.0...3dd3a67bf16474042efac25519ef257d708a8d7b)) + +### Enhancements made + +- Add mermaidjs 10.2.3 [#1957](https://github.com/jupyter/nbconvert/pull/1957) ([@bollwyvl](https://github.com/bollwyvl)) + +### Bugs fixed + +- Fix pdf conversion with explicitly relative paths [#2005](https://github.com/jupyter/nbconvert/pull/2005) ([@tuncbkose](https://github.com/tuncbkose)) +- Ensure TEXINPUTS is an absolute path [#2002](https://github.com/jupyter/nbconvert/pull/2002) ([@tuncbkose](https://github.com/tuncbkose)) + +### Maintenance and upkeep improvements + +- bump pandoc max version [#1997](https://github.com/jupyter/nbconvert/pull/1997) ([@tuncbkose](https://github.com/tuncbkose)) +- exclude bleach 5.0.0 from dependencies resolution [#1990](https://github.com/jupyter/nbconvert/pull/1990) ([@karlicoss](https://github.com/karlicoss)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/nbconvert/graphs/contributors?from=2023-05-08&to=2023-06-13&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Ablink1073+updated%3A2023-05-08..2023-06-13&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Abollwyvl+updated%3A2023-05-08..2023-06-13&type=Issues) | [@karlicoss](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Akarlicoss+updated%3A2023-05-08..2023-06-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Apre-commit-ci+updated%3A2023-05-08..2023-06-13&type=Issues) | [@tuncbkose](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atuncbkose+updated%3A2023-05-08..2023-06-13&type=Issues) + ## 7.4.0 ([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.3.1...32fcf7b26462f5d51d577f8beda9d49cd3a0f441)) @@ -28,8 +68,6 @@ [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Ablink1073+updated%3A2023-04-10..2023-05-08&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Akrassowski+updated%3A2023-04-10..2023-05-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Apre-commit-ci+updated%3A2023-04-10..2023-05-08&type=Issues) | [@tuncbkose](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atuncbkose+updated%3A2023-04-10..2023-05-08&type=Issues) -<!-- <END NEW CHANGELOG ENTRY> --> - ## 7.3.1 ([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.3.0...3860152ecea3d9833540eebe279ff603b3d47cea)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/PKG-INFO new/nbconvert-7.6.0/PKG-INFO --- old/nbconvert-7.4.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: nbconvert -Version: 7.4.0 +Version: 7.6.0 Summary: Converting Jupyter Notebooks Project-URL: Homepage, https://jupyter.org Author-email: Jupyter Development Team <jupy...@googlegroups.com> @@ -45,21 +45,21 @@ Classifier: Programming Language :: Python :: 3 Requires-Python: >=3.7 Requires-Dist: beautifulsoup4 -Requires-Dist: bleach +Requires-Dist: bleach!=5.0.0 Requires-Dist: defusedxml Requires-Dist: importlib-metadata>=3.6; python_version < '3.10' Requires-Dist: jinja2>=3.0 Requires-Dist: jupyter-core>=4.7 Requires-Dist: jupyterlab-pygments Requires-Dist: markupsafe>=2.0 -Requires-Dist: mistune<3,>=2.0.3 +Requires-Dist: mistune<4,>=2.0.3 Requires-Dist: nbclient>=0.5.0 -Requires-Dist: nbformat>=5.1 +Requires-Dist: nbformat>=5.7 Requires-Dist: packaging Requires-Dist: pandocfilters>=1.4.1 Requires-Dist: pygments>=2.4.1 Requires-Dist: tinycss2 -Requires-Dist: traitlets>=5.0 +Requires-Dist: traitlets>=5.1 Provides-Extra: all Requires-Dist: nbconvert[docs,qtpdf,serve,test,webpdf]; extra == 'all' Provides-Extra: docs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/_version.py new/nbconvert-7.6.0/nbconvert/_version.py --- old/nbconvert-7.4.0/nbconvert/_version.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/_version.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,7 +3,7 @@ from typing import List # Version string must appear intact for versioning -__version__ = "7.4.0" +__version__ = "7.6.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/exporters/html.py new/nbconvert-7.6.0/nbconvert/exporters/html.py --- old/nbconvert-7.4.0/nbconvert/exporters/html.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/exporters/html.py 2020-02-02 01:00:00.000000000 +0100 @@ -121,6 +121,15 @@ """, ).tag(config=True) + mermaid_js_url = Unicode( + "https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.2.3/mermaid.esm.min.mjs", + help=""" + URL to load MermaidJS from. + + Defaults to loading from cdnjs. + """, + ) + jquery_url = Unicode( "https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js", help=""" @@ -303,6 +312,7 @@ resources["include_url"] = resources_include_url resources["require_js_url"] = self.require_js_url resources["mathjax_url"] = self.mathjax_url + resources["mermaid_js_url"] = self.mermaid_js_url resources["jquery_url"] = self.jquery_url resources["jupyter_widgets_base_url"] = self.jupyter_widgets_base_url resources["widget_renderer_url"] = self.widget_renderer_url diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/exporters/latex.py new/nbconvert-7.6.0/nbconvert/exporters/latex.py --- old/nbconvert-7.4.0/nbconvert/exporters/latex.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/exporters/latex.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,12 +3,14 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +import os from traitlets import default from traitlets.config import Config from nbconvert.filters.filter_links import resolve_references from nbconvert.filters.highlight import Highlight2Latex +from nbconvert.filters.pandoc import ConvertExplicitlyRelativePaths from .templateexporter import TemplateExporter @@ -77,6 +79,16 @@ ) self.register_filter("highlight_code", highlight_code) + # Need to make sure explicit relative paths are visible to latex for pdf conversion + # https://github.com/jupyter/nbconvert/issues/1998 + nb_path = resources.get("metadata", {}).get("path") if resources else None + texinputs = os.path.abspath(nb_path) if nb_path else os.getcwd() + convert_explicitly_relative_paths = self.filters.get( + "convert_explicitly_relative_paths", + ConvertExplicitlyRelativePaths(texinputs=texinputs, parent=self), + ) + self.register_filter("convert_explicitly_relative_paths", convert_explicitly_relative_paths) + return super().from_notebook_node(nb, resources, **kw) def _create_environment(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/exporters/pdf.py new/nbconvert-7.6.0/nbconvert/exporters/pdf.py --- old/nbconvert-7.4.0/nbconvert/exporters/pdf.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/exporters/pdf.py 2020-02-02 01:00:00.000000000 +0100 @@ -186,7 +186,7 @@ latex, resources = super().from_notebook_node(nb, resources=resources, **kw) # set texinputs directory, so that local files will be found if resources and resources.get("metadata", {}).get("path"): - self.texinputs = resources["metadata"]["path"] + self.texinputs = os.path.abspath(resources["metadata"]["path"]) else: self.texinputs = os.getcwd() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/exporters/tests/test_pdf.py new/nbconvert-7.6.0/nbconvert/exporters/tests/test_pdf.py --- old/nbconvert-7.4.0/nbconvert/exporters/tests/test_pdf.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/exporters/tests/test_pdf.py 2020-02-02 01:00:00.000000000 +0100 @@ -8,6 +8,7 @@ from tempfile import TemporaryDirectory from ...tests.utils import onlyif_cmds_exist +from ...utils import _contextlib_chdir from ..pdf import PDFExporter from .base import ExportersTestsBase @@ -39,3 +40,24 @@ assert len(output) > 0 # all temporary file should be cleaned up assert {file_name} == set(os.listdir(td)) + + @onlyif_cmds_exist("xelatex", "pandoc") + def test_texinputs(self): + """ + Is TEXINPUTS set properly when we are converting + - in the same directory, and + - in a different directory? + """ + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): + os.mkdir("folder") + file_name = os.path.basename(self._get_notebook()) + nb1 = os.path.join(td, file_name) + nb2 = os.path.join(td, "folder", file_name) + ex1 = self.exporter_class(latex_count=1) # type:ignore + ex2 = self.exporter_class(latex_count=1) # type:ignore + shutil.copy(self._get_notebook(), nb1) + shutil.copy(self._get_notebook(), nb2) + _ = ex1.from_filename(nb1) + _ = ex2.from_filename(nb2) + assert ex1.texinputs == os.path.abspath(".") + assert ex2.texinputs == os.path.abspath("./folder") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/filters/markdown.py new/nbconvert-7.6.0/nbconvert/filters/markdown.py --- old/nbconvert-7.4.0/nbconvert/filters/markdown.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/filters/markdown.py 2020-02-02 01:00:00.000000000 +0100 @@ -11,13 +11,14 @@ try: from .markdown_mistune import markdown2html_mistune + except ImportError as e: - # store in variable for Python 3 _mistune_import_error = e - def markdown2html_mistune(source): + def markdown2html_mistune(source: str) -> str: """mistune is unavailable, raise ImportError""" - raise ImportError("markdown2html requires mistune: %s" % _mistune_import_error) + msg = f"markdown2html requires mistune: {_mistune_import_error}" + raise ImportError(msg) from .pandoc import convert_pandoc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/filters/markdown_mistune.py new/nbconvert-7.6.0/nbconvert/filters/markdown_mistune.py --- old/nbconvert-7.4.0/nbconvert/filters/markdown_mistune.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/filters/markdown_mistune.py 2020-02-02 01:00:00.000000000 +0100 @@ -9,143 +9,260 @@ import base64 import mimetypes import os -import re -from functools import partial from html import escape +from typing import Any, Callable, Dict, Iterable, Match, Optional, Tuple import bs4 -from mistune import PLUGINS, BlockParser, HTMLRenderer, InlineParser, Markdown # type:ignore from pygments import highlight from pygments.formatters import HtmlFormatter +from pygments.lexer import Lexer from pygments.lexers import get_lexer_by_name from pygments.util import ClassNotFound from nbconvert.filters.strings import add_anchor -html_escape = partial(escape, quote=False) - +try: # for Mistune >= 3.0 + from mistune import ( + BlockParser, + BlockState, + HTMLRenderer, + InlineParser, + InlineState, + Markdown, + import_plugin, + ) -class InvalidNotebook(Exception): # noqa - """An invalid notebook model.""" + MISTUNE_V3 = True - pass +except ImportError: # for Mistune >= 2.0 + import re + from mistune import ( # type: ignore[attr-defined] + PLUGINS, + BlockParser, + HTMLRenderer, + InlineParser, + Markdown, + ) -class MathBlockParser(BlockParser): - """This acts as a pass-through to the MathInlineParser. It is needed in - order to avoid other block level rules splitting math sections apart. - """ + MISTUNE_V3 = False - MULTILINE_MATH = re.compile( - r"(?<!\\)[$]{2}.*?(?<!\\)[$]{2}|" - r"\\\\\[.*?\\\\\]|" - r"\\begin\{([a-z]*\*?)\}.*?\\end\{\1\}", - re.DOTALL, - ) + def import_plugin(name: str) -> 'MarkdownPlugin': # type: ignore[misc] + """Simple implementation of Mistune V3's import_plugin for V2.""" + return PLUGINS[name] # type: ignore[no-any-return] - RULE_NAMES = ("multiline_math", *BlockParser.RULE_NAMES) - # Regex for header that doesn't require space after '#' - AXT_HEADING = re.compile(r" {0,3}(#{1,6})(?!#+)(?: *\n+|([^\n]*?)(?:\n+|\s+?#+\s*\n+))") +class InvalidNotebook(Exception): # noqa + """An invalid notebook model.""" - def parse_multiline_math(self, m, state): - """Pass token through mutiline math.""" - return {"type": "multiline_math", "text": m.group(0)} + pass -def _dotall(pattern): - """Make the '.' special character match any character inside the pattern, including a newline. +def _dotall(pattern: str) -> str: + """Makes the '.' special character match any character inside the pattern, including a newline. - This is implemented with the inline flag `(?s:...)` and is equivalent to using `re.DOTALL` when - it is the only pattern used. It is necessary since `mistune>=2.0.0`, where the pattern is passed - to the undocumented `re.Scanner`. + This is implemented with the inline flag `(?s:...)` and is equivalent to using `re.DOTALL`. + It is useful for LaTeX environments, where line breaks may be present. """ return f"(?s:{pattern})" -class MathInlineParser(InlineParser): - r"""This interprets the content of LaTeX style math objects. +if MISTUNE_V3: # Parsers for Mistune >= 3.0.0 - In particular this grabs ``$$...$$``, ``\\[...\\]``, ``\\(...\\)``, ``$...$``, - and ``\begin{foo}...\end{foo}`` styles for declaring mathematics. It strips - delimiters from all these varieties, and extracts the type of environment - in the last case (``foo`` in this example). - """ - BLOCK_MATH_TEX = _dotall(r"(?<!\\)\$\$(.*?)(?<!\\)\$\$") - BLOCK_MATH_LATEX = _dotall(r"(?<!\\)\\\\\[(.*?)(?<!\\)\\\\\]") - INLINE_MATH_TEX = _dotall(r"(?<![$\\])\$(.+?)(?<![$\\])\$") - INLINE_MATH_LATEX = _dotall(r"(?<!\\)\\\\\((.*?)(?<!\\)\\\\\)") - LATEX_ENVIRONMENT = _dotall(r"\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}") - - # The order is important here - RULE_NAMES = ( - "block_math_tex", - "block_math_latex", - "inline_math_tex", - "inline_math_latex", - "latex_environment", - *InlineParser.RULE_NAMES, - ) - - def parse_block_math_tex(self, m, state): - """Parse block text math.""" - # sometimes the Scanner keeps the final '$$', so we use the - # full matched string and remove the math markers - text = m.group(0)[2:-2] - return "block_math", text - - def parse_block_math_latex(self, m, state): - """Parse block latex math .""" - text = m.group(1) - return "block_math", text - - def parse_inline_math_tex(self, m, state): - """Parse inline tex math.""" - text = m.group(1) - return "inline_math", text - - def parse_inline_math_latex(self, m, state): - """Parse inline latex math.""" - text = m.group(1) - return "inline_math", text - - def parse_latex_environment(self, m, state): - """Parse a latex environment.""" - name, text = m.group(1), m.group(2) - return "latex_environment", name, text + class MathBlockParser(BlockParser): + """This acts as a pass-through to the MathInlineParser. It is needed in + order to avoid other block level rules splitting math sections apart. + + It works by matching each multiline math environment as a single paragraph, + so that other rules don't think each section is its own paragraph. Inline + is ignored here. + """ + AXT_HEADING_WITHOUT_LEADING_SPACES = ( + r"^ {0,3}(?P<axt_1>#{1,6})(?!#+)(?P<axt_2>[ \t]*(.*?)?)$" + ) + + MULTILINE_MATH = _dotall( + # Display math mode, old TeX delimiter: $$ \sqrt{2} $$ + r"(?<!\\)[$]{2}.*?(?<!\\)[$]{2}" + "|" + # Display math mode, new LaTeX delimiter: \[ \sqrt{2} \] + r"\\\\\[.*?\\\\\]" + "|" + # LaTeX environment: \begin{equation} \sqrt{2} \end{equation} + r"\\begin\{(?P<math_env_name>[a-z]*\*?)\}.*?\\end\{(?P=math_env_name)\}" + ) + + SPECIFICATION = { + **BlockParser.SPECIFICATION, + "axt_heading": AXT_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] + + def parse_multiline_math(self, m: Match[str], state: BlockState) -> int: + """Send mutiline math as a single paragraph to MathInlineParser.""" + matched_text = m[0] + state.add_paragraph(matched_text) + return m.end() + + class MathInlineParser(InlineParser): + r"""This interprets the content of LaTeX style math objects. + + In particular this grabs ``$$...$$``, ``\\[...\\]``, ``\\(...\\)``, ``$...$``, + and ``\begin{foo}...\end{foo}`` styles for declaring mathematics. It strips + delimiters from all these varieties, and extracts the type of environment + in the last case (``foo`` in this example). + """ -class MarkdownWithMath(Markdown): - """Markdown text with math enabled.""" + # Display math mode, using older TeX delimiter: $$ \pi $$ + BLOCK_MATH_TEX = _dotall(r"(?<!\\)\$\$(?P<math_block_tex>.*?)(?<!\\)\$\$") + # Display math mode, using newer LaTeX delimiter: \[ \pi \] + BLOCK_MATH_LATEX = _dotall(r"(?<!\\)\\\\\[(?P<math_block_latex>.*?)(?<!\\)\\\\\]") + # Inline math mode, using older TeX delimiter: $ \pi $ (cannot be empty!) + INLINE_MATH_TEX = _dotall(r"(?<![$\\])\$(?P<math_inline_tex>.+?)(?<![$\\])\$") + # Inline math mode, using newer LaTeX delimiter: \( \pi \) + INLINE_MATH_LATEX = _dotall(r"(?<!\\)\\\\\((?P<math_inline_latex>.*?)(?<!\\)\\\\\)") + # LaTeX math environment: \begin{equation} \pi \end{equation} + LATEX_ENVIRONMENT = _dotall( + r"\\begin\{(?P<math_env_name>[a-z]*\*?)\}" + r"(?P<math_env_body>.*?)" + r"\\end\{(?P=math_env_name)\}" + ) + + SPECIFICATION = { + **InlineParser.SPECIFICATION, + "block_math_tex": BLOCK_MATH_TEX, + "block_math_latex": BLOCK_MATH_LATEX, + "inline_math_tex": INLINE_MATH_TEX, + "inline_math_latex": INLINE_MATH_LATEX, + "latex_environment": LATEX_ENVIRONMENT, + } + + # Block math must be matched first, and all math must come before text + DEFAULT_RULES: Tuple[str, ...] = ( + "block_math_tex", + "block_math_latex", + "inline_math_tex", + "inline_math_latex", + "latex_environment", + *InlineParser.DEFAULT_RULES, + ) # type: ignore[assignment] + + def parse_block_math_tex(self, m: Match[str], state: InlineState) -> int: + """Parse older TeX-style display math.""" + body = m.group("math_block_tex") + state.append_token({"type": "block_math", "raw": body}) + return m.end() + + def parse_block_math_latex(self, m: Match[str], state: InlineState) -> int: + """Parse newer LaTeX-style display math.""" + body = m.group("math_block_latex") + state.append_token({"type": "block_math", "raw": body}) + return m.end() + + def parse_inline_math_tex(self, m: Match[str], state: InlineState) -> int: + """Parse older TeX-style inline math.""" + body = m.group("math_inline_tex") + state.append_token({"type": "inline_math", "raw": body}) + return m.end() + + def parse_inline_math_latex(self, m: Match[str], state: InlineState) -> int: + """Parse newer LaTeX-style inline math.""" + body = m.group("math_inline_latex") + state.append_token({"type": "inline_math", "raw": body}) + return m.end() + + def parse_latex_environment(self, m: Match[str], state: InlineState) -> int: + """Parse a latex environment.""" + attrs = {"name": m.group("math_env_name"), "body": m.group("math_env_body")} + state.append_token({"type": "latex_environment", "attrs": attrs}) + return m.end() + +else: # Parsers for Mistune >= 2.0.0 < 3.0.0 + + class MathBlockParser(BlockParser): # type: ignore[no-redef] + """This acts as a pass-through to the MathInlineParser. It is needed in + order to avoid other block level rules splitting math sections apart. + """ - def __init__(self, renderer, block=None, inline=None, plugins=None): - """Initialize the parser.""" - if block is None: - block = MathBlockParser() - if inline is None: - inline = MathInlineParser(renderer, hard_wrap=False) - if plugins is None: - plugins = [ - # "abbr", - # 'footnotes', - "strikethrough", - "table", - "url", - "task_lists", - "def_list", - ] - _plugins = [] - for p in plugins: - if isinstance(p, str): - _plugins.append(PLUGINS[p]) - else: - _plugins.append(p) - plugins = _plugins - super().__init__(renderer, block, inline, plugins) + MULTILINE_MATH = re.compile( + # Display math mode, old TeX delimiter: $$ \sqrt{2} $$ + r"(?<!\\)[$]{2}.*?(?<!\\)[$]{2}|" + # Display math mode, new LaTeX delimiter: \[ \sqrt{2} \] + r"\\\\\[.*?\\\\\]|" + # LaTeX environment: \begin{equation} \sqrt{2} \end{equation} + r"\\begin\{([a-z]*\*?)\}.*?\\end\{\1\}", + re.DOTALL, + ) + + # Regex for header that doesn't require space after '#' + AXT_HEADING = re.compile(r" {0,3}(#{1,6})(?!#+)(?: *\n+|([^\n]*?)(?:\n+|\s+?#+\s*\n+))") + + # Multiline math must be searched before other rules + RULE_NAMES = ("multiline_math", *BlockParser.RULE_NAMES) # type: ignore + + def parse_multiline_math(self, m: Match[str], state: Any) -> Dict[str, str]: + """Pass token through mutiline math.""" + return {"type": "multiline_math", "text": m.group(0)} + + class MathInlineParser(InlineParser): # type: ignore[no-redef] + r"""This interprets the content of LaTeX style math objects. + + In particular this grabs ``$$...$$``, ``\\[...\\]``, ``\\(...\\)``, ``$...$``, + and ``\begin{foo}...\end{foo}`` styles for declaring mathematics. It strips + delimiters from all these varieties, and extracts the type of environment + in the last case (``foo`` in this example). + """ - def render(self, s): - """Compatibility method with `mistune==0.8.4`.""" - return self.parse(s) + # Display math mode, using older TeX delimiter: $$ \pi $$ + BLOCK_MATH_TEX = _dotall(r"(?<!\\)\$\$(.*?)(?<!\\)\$\$") + # Display math mode, using newer LaTeX delimiter: \[ \pi \] + BLOCK_MATH_LATEX = _dotall(r"(?<!\\)\\\\\[(.*?)(?<!\\)\\\\\]") + # Inline math mode, using older TeX delimiter: $ \pi $ (cannot be empty!) + INLINE_MATH_TEX = _dotall(r"(?<![$\\])\$(.+?)(?<![$\\])\$") + # Inline math mode, using newer LaTeX delimiter: \( \pi \) + INLINE_MATH_LATEX = _dotall(r"(?<!\\)\\\\\((.*?)(?<!\\)\\\\\)") + # LaTeX math environment: \begin{equation} \pi \end{equation} + LATEX_ENVIRONMENT = _dotall(r"\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}") + + RULE_NAMES = ( + "block_math_tex", + "block_math_latex", + "inline_math_tex", + "inline_math_latex", + "latex_environment", + *InlineParser.RULE_NAMES, # type: ignore + ) + + def parse_block_math_tex(self, m: Match[str], state: Any) -> Tuple[str, str]: + """Parse block text math.""" + # sometimes the Scanner keeps the final '$$', so we use the + # full matched string and remove the math markers + text = m.group(0)[2:-2] + return "block_math", text + + def parse_block_math_latex(self, m: Match[str], state: Any) -> Tuple[str, str]: + """Parse block latex math .""" + text = m.group(1) + return "block_math", text + + def parse_inline_math_tex(self, m: Match[str], state: Any) -> Tuple[str, str]: + """Parse inline tex math.""" + text = m.group(1) + return "inline_math", text + + def parse_inline_math_latex(self, m: Match[str], state: Any) -> Tuple[str, str]: + """Parse inline latex math.""" + text = m.group(1) + return "inline_math", text + + def parse_latex_environment(self, m: Match[str], state: Any) -> Tuple[str, str, str]: + """Parse a latex environment.""" + name, text = m.group(1), m.group(2) + return "latex_environment", name, text class IPythonRenderer(HTMLRenderer): @@ -153,13 +270,13 @@ def __init__( # noqa self, - escape=True, - allow_harmful_protocols=True, - embed_images=False, - exclude_anchor_links=False, - anchor_link_text="¶", - path="", - attachments=None, + escape: bool = True, + allow_harmful_protocols: bool = True, + embed_images: bool = False, + exclude_anchor_links: bool = False, + anchor_link_text: str = "¶", + path: str = "", + attachments: Optional[Dict[str, Dict[str, str]]] = None, ): """Initialize the renderer.""" super().__init__(escape, allow_harmful_protocols) @@ -172,75 +289,101 @@ else: self.attachments = {} - def block_code(self, code, info=None): + def block_code(self, code: str, info: Optional[str] = None) -> str: """Handle block code.""" - lang = "" - lexer = None + lang: Optional[str] = "" + lexer: Optional[Lexer] = None + if info: + if info.startswith("mermaid"): + return self.block_mermaidjs(code) + try: - lang = info.strip().split(None, 1)[0] + lang = info.strip().split(maxsplit=1)[0] lexer = get_lexer_by_name(lang, stripall=True) except ClassNotFound: - code = lang + "\n" + code - lang = None # type:ignore + code = f"{lang}\n{code}" + lang = None if not lang: - return super().block_code(code) + return super().block_code(code, info=info) formatter = HtmlFormatter() return highlight(code, lexer, formatter) - def block_html(self, html): + def block_mermaidjs(self, code: str) -> str: + """Handle mermaid syntax.""" + return ( + """<div class="jp-Mermaid"><pre class="mermaid">\n""" + f"""{code.strip()}""" + """\n</pre></div>""" + ) + + def block_html(self, html: str) -> str: """Handle block html.""" if self.embed_images: html = self._html_embed_images(html) return super().block_html(html) - def inline_html(self, html): + def inline_html(self, html: str) -> str: """Handle inline html.""" if self.embed_images: html = self._html_embed_images(html) return super().inline_html(html) - def heading(self, text, level): + def heading(self, text: str, level: int, **attrs: Dict[str, Any]) -> str: """Handle a heading.""" - html = super().heading(text, level) + html = super().heading(text, level, **attrs) if self.exclude_anchor_links: return html - return add_anchor(html, anchor_link_text=self.anchor_link_text) + return str(add_anchor(html, anchor_link_text=self.anchor_link_text)) - def escape_html(self, text): + def escape_html(self, text: str) -> str: """Escape html content.""" - return html_escape(text) + return escape(text, quote=False) - def multiline_math(self, text): - """Handle mulitline math.""" - return text - - def block_math(self, text): + def block_math(self, body: str) -> str: """Handle block math.""" - return f"$${self.escape_html(text)}$$" + return f"$${self.escape_html(body)}$$" + + def multiline_math(self, text: str) -> str: + """Handle mulitline math for older mistune versions.""" + return text - def latex_environment(self, name, text): + def latex_environment(self, name: str, body: str) -> str: """Handle a latex environment.""" - name, text = self.escape_html(name), self.escape_html(text) - return f"\\begin{{{name}}}{text}\\end{{{name}}}" + name, body = self.escape_html(name), self.escape_html(body) + return f"\\begin{{{name}}}{body}\\end{{{name}}}" - def inline_math(self, text): + def inline_math(self, body: str) -> str: """Handle inline math.""" - return f"${self.escape_html(text)}$" + return f"${self.escape_html(body)}$" - def image(self, src, text, title): + def image(self, text: str, url: str, title: Optional[str] = None) -> str: """Rendering a image with title and text. - :param src: source link of the image. :param text: alt text of the image. + :param url: source link of the image. :param title: title text of the image. + + :note: The parameters `text` and `url` are swapped in older versions + of mistune. + """ + if MISTUNE_V3: + url = self._embed_image_or_attachment(url) + else: # for mistune v2, the first argument is the URL + text = self._embed_image_or_attachment(text) + + return super().image(text, url, title) + + def _embed_image_or_attachment(self, src: str) -> str: + """Embed an image or attachment, depending on the configuration. + If neither is possible, returns the original URL. """ - attachment_prefix = "attachment:" + attachment_prefix = "attachment:" if src.startswith(attachment_prefix): name = src[len(attachment_prefix) :] @@ -250,25 +393,22 @@ attachment = self.attachments[name] # we choose vector over raster, and lossless over lossy - preferred_mime_types = ["image/svg+xml", "image/png", "image/jpeg"] - for preferred_mime_type in preferred_mime_types: - if preferred_mime_type in attachment: - break - else: # otherwise we choose the first mimetype we can find - preferred_mime_type = list(attachment.keys())[0] - mime_type = preferred_mime_type - data = attachment[mime_type] - src = "data:" + mime_type + ";base64," + data + preferred_mime_types = ("image/svg+xml", "image/png", "image/jpeg") + for mime_type in preferred_mime_types: + if mime_type in attachment: + return f"data:{mime_type};base64,{attachment[mime_type]}" + # otherwise we choose the first mimetype we can find + default_mime_type = tuple(attachment.keys())[0] + return f"data:{default_mime_type};base64,{attachment[default_mime_type]}" elif self.embed_images: base64_url = self._src_to_base64(src) - if base64_url is not None: - src = base64_url + return base64_url - return super().image(src, text, title) + return src - def _src_to_base64(self, src): + def _src_to_base64(self, src: str) -> Optional[str]: """Turn the source file into a base64 url. :param src: source link of the file. @@ -280,30 +420,72 @@ return None with open(src_path, "rb") as fobj: - mime_type = mimetypes.guess_type(src_path)[0] + mime_type, _ = mimetypes.guess_type(src_path) base64_data = base64.b64encode(fobj.read()) base64_str = base64_data.replace(b"\n", b"").decode("ascii") return f"data:{mime_type};base64,{base64_str}" - def _html_embed_images(self, html): + def _html_embed_images(self, html: str) -> str: parsed_html = bs4.BeautifulSoup(html, features="html.parser") - imgs = parsed_html.find_all("img") + imgs: bs4.ResultSet[bs4.Tag] = parsed_html.find_all("img") # Replace img tags's sources by base64 dataurls for img in imgs: - if "src" not in img.attrs: + src = img.attrs.get("src") + if src is None: continue base64_url = self._src_to_base64(img.attrs["src"]) - if base64_url is not None: img.attrs["src"] = base64_url return str(parsed_html) -def markdown2html_mistune(source): +# Represents an already imported plugin for Mistune +MarkdownPlugin = Callable[[Markdown], None] + + +class MarkdownWithMath(Markdown): + """Markdown text with math enabled.""" + + DEFAULT_PLUGINS = ( + # "abbr", (see https://github.com/jupyter/nbconvert/pull/1853) + # "footnotes", + "strikethrough", + "table", + "url", + "task_lists", + "def_list", + ) + + def __init__( + self, + renderer: HTMLRenderer, + block: Optional[BlockParser] = None, + inline: Optional[InlineParser] = None, + plugins: Optional[Iterable[MarkdownPlugin]] = None, + ): + """Initialize the parser.""" + if block is None: + block = MathBlockParser() + if inline is None: + if MISTUNE_V3: + inline = MathInlineParser(hard_wrap=False) + else: + inline = MathInlineParser(renderer, hard_wrap=False) # type: ignore + if plugins is None: + plugins = (import_plugin(p) for p in self.DEFAULT_PLUGINS) + + super().__init__(renderer, block, inline, plugins) + + def render(self, source: str) -> str: + """Render the HTML output for a Markdown source.""" + return str(super().__call__(source)) + + +def markdown2html_mistune(source: str) -> str: """Convert a markdown string to HTML using mistune""" return MarkdownWithMath(renderer=IPythonRenderer(escape=False)).render(source) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/filters/pandoc.py new/nbconvert-7.6.0/nbconvert/filters/pandoc.py --- old/nbconvert-7.4.0/nbconvert/filters/pandoc.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/filters/pandoc.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,4 +1,12 @@ -"""Convert between any two formats using pandoc.""" +""" +Convert between any two formats using pandoc, +and related filters +""" +import os + +from pandocfilters import Image, applyJSONFilters # type:ignore + +from nbconvert.utils.base import NbConvertBase from nbconvert.utils.pandoc import pandoc @@ -23,3 +31,48 @@ Output as returned by pandoc. """ return pandoc(source, from_format, to_format, extra_args=extra_args) + + +# When converting to pdf, explicitly relative references +# like "./" and "../" doesn't work with TEXINPUTS. +# So we need to convert them to absolute paths. +# See https://github.com/jupyter/nbconvert/issues/1998 +class ConvertExplicitlyRelativePaths(NbConvertBase): + """A converter that handles relative path references.""" + + def __init__(self, texinputs=None, **kwargs): + """Initialize the converter.""" + # texinputs should be the directory of the notebook file + self.nb_dir = os.path.abspath(texinputs) if texinputs else "" + self.ancestor_dirs = self.nb_dir.split("/") + super().__init__(**kwargs) + + def __call__(self, source): + """Invoke the converter.""" + # If this is not set for some reason, we can't do anything, + if self.nb_dir: + return applyJSONFilters([self.action], source) + return source + + def action(self, key, value, frmt, meta): + """Perform the action.""" + # Convert explicitly relative paths: + # ./path -> path (This should be visible to the latex engine since TEXINPUTS already has .) + # ../path -> /abs_path + # assuming all relative references are at the start of a given path + if key == "Image": + # Image seems to have this composition, according to https://github.com/jgm/pandoc-types + attr, caption, [filename, typedef] = value + + if filename[:2] == "./": + filename = filename[2:] + elif filename[:3] == "../": + n_up = 0 + while filename[:3] == "../": + n_up += 1 + filename = filename[3:] + ancestors = "/".join(self.ancestor_dirs[:-n_up]) + "/" + filename = ancestors + filename + return Image(attr, caption, [filename, typedef]) + # If not image, return "no change" + return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/filters/tests/test_markdown.py new/nbconvert-7.6.0/nbconvert/filters/tests/test_markdown.py --- old/nbconvert-7.4.0/nbconvert/filters/tests/test_markdown.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/filters/tests/test_markdown.py 2020-02-02 01:00:00.000000000 +0100 @@ -247,6 +247,19 @@ tokens[index], ) + def test_mermaid_markdown(self): + code = """flowchart LR + chicken --> egg --> chicken""" + case = f"""```mermaid\n {code}\n```""" + + output_check = ( + """<div class="jp-Mermaid"><pre class="mermaid">\n""" + f"""{code.strip()}""" + """\n</pre></div>""" + ) + + self._try_markdown(markdown2html, case, output_check) + def _try_markdown(self, method, test, tokens): results = method(test) if isinstance(tokens, (str,)): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/filters/tests/test_pandoc.py new/nbconvert-7.6.0/nbconvert/filters/tests/test_pandoc.py --- old/nbconvert-7.4.0/nbconvert/filters/tests/test_pandoc.py 1970-01-01 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/filters/tests/test_pandoc.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,59 @@ +""" +Module with tests for Pandoc filters +""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +import json + +from ...tests.base import TestsBase +from ...tests.utils import onlyif_cmds_exist +from ..pandoc import ConvertExplicitlyRelativePaths, convert_pandoc + + +class TestPandocFilters(TestsBase): + @onlyif_cmds_exist("pandoc") + def test_convert_explicitly_relative_paths(self): + """ + Do the image links in a markdown file located in dir get processed correctly? + """ + inp_dir = "/home/user/src" + fltr = ConvertExplicitlyRelativePaths(texinputs=inp_dir) + + # pairs of input, expected + tests = { + # TEXINPUTS is enough, abs_path not needed + "im.png": "im.png", + "./im.png": "im.png", + "./images/im.png": "images/im.png", + # TEXINPUTS is not enough, abs_path needed + "../im.png": "/home/user/im.png", + "../images/im.png": "/home/user/images/im.png", + "../../images/im.png": "/home/images/im.png", + } + + # this shouldn't be modified by the filter + # since it is a code block inside markdown, + # not an image link itself + fake = """ + ``` + \\includegraphics{../fake.png} + ``` + """ + + # convert to markdown image + def foo(filename): + return f"" + + # create input markdown and convert to pandoc json + inp = convert_pandoc(fake + "\n".join(map(foo, tests.keys())), "markdown", "json") + expected = convert_pandoc(fake + "\n".join(map(foo, tests.values())), "markdown", "json") + # Do this to fix string formatting + expected = json.dumps(json.loads(expected)) + self.assertEqual(expected, fltr(inp)) + + def test_convert_explicitly_relative_paths_no_texinputs(self): + # no texinputs should lead to just returning + fltr = ConvertExplicitlyRelativePaths(texinputs="") + self.assertEqual("test", fltr("test")) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/nbconvert/utils/pandoc.py new/nbconvert-7.6.0/nbconvert/utils/pandoc.py --- old/nbconvert-7.4.0/nbconvert/utils/pandoc.py 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/nbconvert/utils/pandoc.py 2020-02-02 01:00:00.000000000 +0100 @@ -13,8 +13,8 @@ from .exceptions import ConversionException -_minimal_version = "1.12.1" -_maximal_version = "3.0.0" +_minimal_version = "2.14.2" +_maximal_version = "4.0.0" def pandoc(source, fmt, to, extra_args=None, encoding="utf-8"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/pyproject.toml new/nbconvert-7.6.0/pyproject.toml --- old/nbconvert-7.4.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -21,21 +21,21 @@ requires-python = ">=3.7" dependencies = [ "beautifulsoup4", - "bleach", + "bleach!=5.0.0", "defusedxml", "importlib_metadata>=3.6;python_version<\"3.10\"", "jinja2>=3.0", "jupyter_core>=4.7", "jupyterlab_pygments", "MarkupSafe>=2.0", - "mistune>=2.0.3,<3", + "mistune>=2.0.3,<4", "nbclient>=0.5.0", - "nbformat>=5.1", + "nbformat>=5.7", "packaging", "pandocfilters>=1.4.1", "pygments>=2.4.1", "tinycss2", - "traitlets>=5.0", + "traitlets>=5.1", ] dynamic = ["version"] @@ -129,7 +129,7 @@ "black[jupyter]==23.3.0", "mdformat>0.7", "mdformat-gfm>=0.3.5", - "ruff==0.0.263" + "ruff==0.0.270" ] detached = true [tool.hatch.envs.lint.scripts] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/share/templates/classic/index.html.j2 new/nbconvert-7.6.0/share/templates/classic/index.html.j2 --- old/nbconvert-7.4.0/share/templates/classic/index.html.j2 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/share/templates/classic/index.html.j2 2020-02-02 01:00:00.000000000 +0100 @@ -19,6 +19,12 @@ {%- block html_head_js_requirejs -%} <script src="{{ resources.require_js_url }}"></script> {%- endblock html_head_js_requirejs -%} +{%- block html_head_js_mermaidjs -%} +<script type="module"> + import mermaid from '{{ resources.mermaid_js_url }}'; + mermaid.initialize({ startOnLoad: true }); +</script> +{%- endblock html_head_js_mermaidjs -%} {%- endblock html_head_js -%} {% block jupyter_widgets %} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/share/templates/lab/index.html.j2 new/nbconvert-7.6.0/share/templates/lab/index.html.j2 --- old/nbconvert-7.4.0/share/templates/lab/index.html.j2 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/share/templates/lab/index.html.j2 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,6 @@ {%- extends 'base.html.j2' -%} {% from 'mathjax.html.j2' import mathjax %} +{% from 'mermaidjs.html.j2' import mermaid_js %} {% from 'jupyter_widgets.html.j2' import jupyter_widgets %} {%- block header -%} @@ -149,6 +150,10 @@ {{ mathjax(resources.mathjax_url) }} {%- endblock html_head_js_mathjax -%} +{%- block html_head_js_mermaidjs -%} +{{ mermaid_js(resources.mermaid_js_url) }} +{%- endblock html_head_js_mermaidjs -%} + {%- block html_head_css -%} {%- endblock html_head_css -%} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/share/templates/lab/mermaidjs.html.j2 new/nbconvert-7.6.0/share/templates/lab/mermaidjs.html.j2 --- old/nbconvert-7.4.0/share/templates/lab/mermaidjs.html.j2 1970-01-01 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/share/templates/lab/mermaidjs.html.j2 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,114 @@ +{%- macro mermaid_js( +url="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.0.2/mermaid.esm.min.mjs" +) -%} +<script type="module"> + document.addEventListener("DOMContentLoaded", async () => { + const diagrams = document.querySelectorAll(".jp-Mermaid > pre.mermaid"); + // do not load mermaidjs if not needed + if (!diagrams.length) { + return; + } + const mermaid = (await import("{{ url }}")).default; + + mermaid.initialize({ + maxTextSize: 100000, + startOnLoad: false, + fontFamily: window + .getComputedStyle(document.body) + .getPropertyValue("--jp-ui-font-family"), + theme: document.querySelector("body[data-jp-theme-light='true']") + ? "default" + : "dark", + }); + + let _nextMermaidId = 0; + + function makeMermaidImage(svg) { + const img = document.createElement('img'); + const maxWidth = svg.match(/max-width: (\d+)/); + if (maxWidth && maxWidth[1]) { + const width = parseInt(maxWidth[1]); + if (width && !Number.isNaN(width) && Number.isFinite(width)) { + img.width = width; + } + } + img.setAttribute('src', `data:image/svg+xml,${encodeURIComponent(svg)}`); + return img; + } + + async function makeMermaidError(text) { + let errorMessage = ''; + try { + await mermaid.parse(text); + } catch (err) { + errorMessage = `${err}`; + } + + const result = document.createElement('details'); + const summary = document.createElement('summary'); + const pre = document.createElement('pre'); + const code = document.createElement('code'); + code.innerText = text; + pre.appendChild(code); + summary.appendChild(pre); + result.appendChild(summary); + + const warning = document.createElement('pre'); + warning.innerText = errorMessage; + result.appendChild(warning); + return result; + } + + async function renderOneMarmaid(src) { + const id = `jp-mermaid-${_nextMermaidId++}`; + const parent = src.parentNode; + let raw = src.textContent.trim(); + const el = document.createElement("div"); + el.style.visibility = "hidden"; + document.body.appendChild(el); + let result = null; + try { + const { svg } = await mermaid.render(id, raw, el); + result = makeMermaidImage(svg); + } catch (err) { + parent.classList.add("jp-mod-warning"); + result = await makeMermaidError(raw); + } finally { + el.remove(); + } + parent.classList.add("jp-RenderedMermaid"); + parent.appendChild(result); + } + + void Promise.all([...diagrams].map(renderOneMarmaid)); + }); +</script> +<style> + .jp-RenderedMarkdown .jp-Mermaid:not(.jp-RenderedMermaid) { + display: none; + } + .jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning { + width: auto; + padding: 10px; + border: var(--jp-border-width) solid var(--jp-warn-color2); + border-radius: var(--jp-border-radius); + color: var(--jp-ui-font-color1); + font-size: var(--jp-ui-font-size1); + white-space: pre-wrap; + word-wrap: break-word; + } + .jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning details > pre { + margin-top: 1em; + } + .jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning summary { + color: var(--jp-warn-color2); + } + .jp-RenderedMarkdown .jp-RenderedMermaid.jp-mod-warning summary > pre { + display: inline-block; + } + .jp-RenderedMermaid > .mermaid { + display: none; + } +</style> +<!-- End of mermaid configuration --> +{%- endmacro %} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/share/templates/latex/base.tex.j2 new/nbconvert-7.6.0/share/templates/latex/base.tex.j2 --- old/nbconvert-7.4.0/share/templates/latex/base.tex.j2 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/share/templates/latex/base.tex.j2 2020-02-02 01:00:00.000000000 +0100 @@ -89,6 +89,7 @@ \usepackage[inline]{enumitem} % IRkernel/repr support (it uses the enumerate* environment) \usepackage[normalem]{ulem} % ulem is needed to support strikethroughs (\sout) % normalem makes italics be italics, not underlines + \usepackage{soul} % strikethrough (\st) support for pandoc >= 3.0.0 \usepackage{mathrsfs} ((* endblock packages *)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/share/templates/latex/document_contents.tex.j2 new/nbconvert-7.6.0/share/templates/latex/document_contents.tex.j2 --- old/nbconvert-7.4.0/share/templates/latex/document_contents.tex.j2 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/share/templates/latex/document_contents.tex.j2 2020-02-02 01:00:00.000000000 +0100 @@ -65,7 +65,7 @@ % Render markdown ((* block markdowncell scoped *)) - ((( cell.source | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'json',extra_args=[]) | resolve_references | convert_pandoc('json','latex')))) + ((( cell.source | citation2latex | strip_files_prefix | convert_pandoc('markdown+tex_math_double_backslash', 'json',extra_args=[]) | resolve_references | convert_explicitly_relative_paths | convert_pandoc('json','latex')))) ((* endblock markdowncell *)) % Don't display unknown types diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbconvert-7.4.0/share/templates/reveal/index.html.j2 new/nbconvert-7.6.0/share/templates/reveal/index.html.j2 --- old/nbconvert-7.4.0/share/templates/reveal/index.html.j2 2020-02-02 01:00:00.000000000 +0100 +++ new/nbconvert-7.6.0/share/templates/reveal/index.html.j2 2020-02-02 01:00:00.000000000 +0100 @@ -30,6 +30,12 @@ {%- block html_head_js_requirejs -%} <script src="{{ resources.require_js_url }}"></script> {%- endblock html_head_js_requirejs -%} +{%- block html_head_js_mermaidjs -%} +<script type="module"> + import mermaid from '{{ resources.mermaid_js_url }}'; + mermaid.initialize({ startOnLoad: true }); +</script> +{%- endblock html_head_js_mermaidjs -%} {%- endblock html_head_js -%} {% block jupyter_widgets %}