Gentoo Archives: gentoo-commits

From: "Michał Górny" <mgorny@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] repo/gentoo:master commit in: dev-python/nbconvert/, dev-python/nbconvert/files/
Date: Fri, 27 May 2022 06:10:46
Message-Id: 1653631827.f66a4fc204598c2abfe388b680694e3ff0d2dbbb.mgorny@gentoo
1 commit: f66a4fc204598c2abfe388b680694e3ff0d2dbbb
2 Author: Michał Górny <mgorny <AT> gentoo <DOT> org>
3 AuthorDate: Fri May 27 04:54:04 2022 +0000
4 Commit: Michał Górny <mgorny <AT> gentoo <DOT> org>
5 CommitDate: Fri May 27 06:10:27 2022 +0000
6 URL: https://gitweb.gentoo.org/repo/gentoo.git/commit/?id=f66a4fc2
7
8 dev-python/nbconvert: Support mistune-2
9
10 Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>
11
12 .../files/nbconvert-6.5.0-mistune-2.patch | 339 +++++++++++++++++++++
13 dev-python/nbconvert/nbconvert-6.5.0-r1.ebuild | 82 +++++
14 2 files changed, 421 insertions(+)
15
16 diff --git a/dev-python/nbconvert/files/nbconvert-6.5.0-mistune-2.patch b/dev-python/nbconvert/files/nbconvert-6.5.0-mistune-2.patch
17 new file mode 100644
18 index 000000000000..4a3f4731b32d
19 --- /dev/null
20 +++ b/dev-python/nbconvert/files/nbconvert-6.5.0-mistune-2.patch
21 @@ -0,0 +1,339 @@
22 +From 6e5ba41803cc8c3192f001b3ede9b74454220bda Mon Sep 17 00:00:00 2001
23 +From: Tiago de Paula <tiagodepalves@×××××.com>
24 +Date: Mon, 9 May 2022 09:39:31 -0300
25 +Subject: [PATCH] Update to Mistune 2.0.2 (#1764)
26 +
27 +Co-authored-by: Steven Silvester <steven.silvester@××××.org>
28 +---
29 + nbconvert/filters/markdown_mistune.py | 212 ++++++++++++++------------
30 + setup.py | 2 +-
31 + 2 files changed, 119 insertions(+), 95 deletions(-)
32 +
33 +diff --git a/nbconvert/filters/markdown_mistune.py b/nbconvert/filters/markdown_mistune.py
34 +index 382a5388..636e1e8c 100644
35 +--- a/nbconvert/filters/markdown_mistune.py
36 ++++ b/nbconvert/filters/markdown_mistune.py
37 +@@ -21,7 +21,7 @@ except ImportError:
38 + from cgi import escape as html_escape
39 +
40 + import bs4
41 +-import mistune
42 ++from mistune import BlockParser, HTMLRenderer, InlineParser, Markdown
43 + from pygments import highlight
44 + from pygments.formatters import HtmlFormatter
45 + from pygments.lexers import get_lexer_by_name
46 +@@ -34,158 +34,183 @@ class InvalidNotebook(Exception):
47 + pass
48 +
49 +
50 +-class MathBlockGrammar(mistune.BlockGrammar):
51 +- """This defines a single regex comprised of the different patterns that
52 +- identify math content spanning multiple lines. These are used by the
53 +- MathBlockLexer.
54 ++class MathBlockParser(BlockParser):
55 ++ """This acts as a pass-through to the MathInlineParser. It is needed in
56 ++ order to avoid other block level rules splitting math sections apart.
57 + """
58 +
59 +- multi_math_str = "|".join(
60 +- [r"^\$\$.*?\$\$", r"^\\\\\[.*?\\\\\]", r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}"]
61 ++ MULTILINE_MATH = re.compile(
62 ++ r"(?<!\\)[$]{2}.*?(?<!\\)[$]{2}|"
63 ++ r"\\\\\[.*?\\\\\]|"
64 ++ r"\\begin\{([a-z]*\*?)\}.*?\\end\{\1\}",
65 ++ re.DOTALL,
66 + )
67 +- multiline_math = re.compile(multi_math_str, re.DOTALL)
68 +
69 ++ RULE_NAMES = ("multiline_math",) + BlockParser.RULE_NAMES
70 +
71 +-class MathBlockLexer(mistune.BlockLexer):
72 +- """This acts as a pass-through to the MathInlineLexer. It is needed in
73 +- order to avoid other block level rules splitting math sections apart.
74 +- """
75 ++ # Regex for header that doesn't require space after '#'
76 ++ AXT_HEADING = re.compile(r" {0,3}(#{1,6})(?!#+)\s*([^\n]*?)$")
77 +
78 +- default_rules = ["multiline_math"] + mistune.BlockLexer.default_rules
79 ++ def parse_multiline_math(self, m, state):
80 ++ """Pass token through mutiline math."""
81 ++ return {"type": "multiline_math", "text": m.group(0)}
82 +
83 +- def __init__(self, rules=None, **kwargs):
84 +- if rules is None:
85 +- rules = MathBlockGrammar()
86 +- super().__init__(rules, **kwargs)
87 +
88 +- def parse_multiline_math(self, m):
89 +- """Add token to pass through mutiline math."""
90 +- self.tokens.append({"type": "multiline_math", "text": m.group(0)})
91 ++def _dotall(pattern):
92 ++ """Make the '.' special character match any character inside the pattern, including a newline.
93 +
94 +-
95 +-class MathInlineGrammar(mistune.InlineGrammar):
96 +- """This defines different ways of declaring math objects that should be
97 +- passed through to mathjax unaffected. These are used by the MathInlineLexer.
98 ++ This is implemented with the inline flag `(?s:...)` and is equivalent to using `re.DOTALL` when
99 ++ it is the only pattern used. It is necessary since `mistune>=2.0.0`, where the pattern is passed
100 ++ to the undocumented `re.Scanner`.
101 + """
102 +-
103 +- inline_math = re.compile(r"^\$(.+?)\$|^\\\\\((.+?)\\\\\)", re.DOTALL)
104 +- block_math = re.compile(r"^\$\$(.*?)\$\$|^\\\\\[(.*?)\\\\\]", re.DOTALL)
105 +- latex_environment = re.compile(r"^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}", re.DOTALL)
106 +- text = re.compile(r"^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)")
107 ++ return f"(?s:{pattern})"
108 +
109 +
110 +-class MathInlineLexer(mistune.InlineLexer):
111 +- r"""This interprets the content of LaTeX style math objects using the rules
112 +- defined by the MathInlineGrammar.
113 ++class MathInlineParser(InlineParser):
114 ++ r"""This interprets the content of LaTeX style math objects.
115 +
116 + In particular this grabs ``$$...$$``, ``\\[...\\]``, ``\\(...\\)``, ``$...$``,
117 + and ``\begin{foo}...\end{foo}`` styles for declaring mathematics. It strips
118 + delimiters from all these varieties, and extracts the type of environment
119 + in the last case (``foo`` in this example).
120 + """
121 +- default_rules = [
122 +- "block_math",
123 +- "inline_math",
124 ++ BLOCK_MATH_TEX = _dotall(r"(?<!\\)\$\$(.*?)(?<!\\)\$\$")
125 ++ BLOCK_MATH_LATEX = _dotall(r"(?<!\\)\\\\\[(.*?)(?<!\\)\\\\\]")
126 ++ INLINE_MATH_TEX = _dotall(r"(?<![$\\])\$(.+?)(?<![$\\])\$")
127 ++ INLINE_MATH_LATEX = _dotall(r"(?<!\\)\\\\\((.*?)(?<!\\)\\\\\)")
128 ++ LATEX_ENVIRONMENT = _dotall(r"\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}")
129 ++
130 ++ # The order is important here
131 ++ RULE_NAMES = (
132 ++ "block_math_tex",
133 ++ "block_math_latex",
134 ++ "inline_math_tex",
135 ++ "inline_math_latex",
136 + "latex_environment",
137 +- ] + mistune.InlineLexer.default_rules
138 +-
139 +- def __init__(self, renderer, rules=None, **kwargs):
140 +- if rules is None:
141 +- rules = MathInlineGrammar()
142 +- super().__init__(renderer, rules, **kwargs)
143 +-
144 +- def output_inline_math(self, m):
145 +- return self.renderer.inline_math(m.group(1) or m.group(2))
146 +-
147 +- def output_block_math(self, m):
148 +- return self.renderer.block_math(m.group(1) or m.group(2) or "")
149 +-
150 +- def output_latex_environment(self, m):
151 +- return self.renderer.latex_environment(m.group(1), m.group(2))
152 +-
153 +-
154 +-class MarkdownWithMath(mistune.Markdown):
155 +- def __init__(self, renderer, **kwargs):
156 +- if "inline" not in kwargs:
157 +- kwargs["inline"] = MathInlineLexer
158 +- if "block" not in kwargs:
159 +- kwargs["block"] = MathBlockLexer
160 +- super().__init__(renderer, **kwargs)
161 +-
162 +- def output_multiline_math(self):
163 +- return self.inline(self.token["text"])
164 +-
165 +-
166 +-class IPythonRenderer(mistune.Renderer):
167 +- def block_code(self, code, lang):
168 +- if lang:
169 ++ ) + InlineParser.RULE_NAMES
170 ++
171 ++ def parse_block_math_tex(self, m, state):
172 ++ # sometimes the Scanner keeps the final '$$', so we use the
173 ++ # full matched string and remove the math markers
174 ++ text = m.group(0)[2:-2]
175 ++ return "block_math", text
176 ++
177 ++ def parse_block_math_latex(self, m, state):
178 ++ text = m.group(1)
179 ++ return "block_math", text
180 ++
181 ++ def parse_inline_math_tex(self, m, state):
182 ++ text = m.group(1)
183 ++ return "inline_math", text
184 ++
185 ++ def parse_inline_math_latex(self, m, state):
186 ++ text = m.group(1)
187 ++ return "inline_math", text
188 ++
189 ++ def parse_latex_environment(self, m, state):
190 ++ name, text = m.group(1), m.group(2)
191 ++ return "latex_environment", name, text
192 ++
193 ++
194 ++class MarkdownWithMath(Markdown):
195 ++ def __init__(self, renderer, block=None, inline=None, plugins=None):
196 ++ if block is None:
197 ++ block = MathBlockParser()
198 ++ if inline is None:
199 ++ inline = MathInlineParser(renderer, hard_wrap=False)
200 ++ super().__init__(renderer, block, inline, plugins)
201 ++
202 ++ def render(self, s):
203 ++ """Compatibility method with `mistune==0.8.4`."""
204 ++ return self.parse(s)
205 ++
206 ++
207 ++class IPythonRenderer(HTMLRenderer):
208 ++ def __init__(
209 ++ self,
210 ++ escape=True,
211 ++ allow_harmful_protocols=True,
212 ++ embed_images=False,
213 ++ exclude_anchor_links=False,
214 ++ anchor_link_text="¶",
215 ++ path="",
216 ++ attachments=None,
217 ++ ):
218 ++ super().__init__(escape, allow_harmful_protocols)
219 ++ self.embed_images = embed_images
220 ++ self.exclude_anchor_links = exclude_anchor_links
221 ++ self.anchor_link_text = anchor_link_text
222 ++ self.path = path
223 ++ if attachments is not None:
224 ++ self.attachments = attachments
225 ++ else:
226 ++ self.attachments = {}
227 ++
228 ++ def block_code(self, code, info=None):
229 ++ if info:
230 + try:
231 ++ lang = info.strip().split(None, 1)[0]
232 + lexer = get_lexer_by_name(lang, stripall=True)
233 + except ClassNotFound:
234 + code = lang + "\n" + code
235 + lang = None
236 +
237 + if not lang:
238 +- return "\n<pre><code>%s</code></pre>\n" % mistune.escape(code)
239 ++ return super().block_code(code)
240 +
241 + formatter = HtmlFormatter()
242 + return highlight(code, lexer, formatter)
243 +
244 + def block_html(self, html):
245 +- embed_images = self.options.get("embed_images", False)
246 +-
247 +- if embed_images:
248 ++ if self.embed_images:
249 + html = self._html_embed_images(html)
250 +
251 + return super().block_html(html)
252 +
253 + def inline_html(self, html):
254 +- embed_images = self.options.get("embed_images", False)
255 +-
256 +- if embed_images:
257 ++ if self.embed_images:
258 + html = self._html_embed_images(html)
259 +
260 + return super().inline_html(html)
261 +
262 +- def header(self, text, level, raw=None):
263 +- html = super().header(text, level, raw=raw)
264 +- if self.options.get("exclude_anchor_links"):
265 ++ def heading(self, text, level):
266 ++ html = super().heading(text, level)
267 ++ if self.exclude_anchor_links:
268 + return html
269 +- anchor_link_text = self.options.get("anchor_link_text", "¶")
270 +- return add_anchor(html, anchor_link_text=anchor_link_text)
271 ++ return add_anchor(html, anchor_link_text=self.anchor_link_text)
272 +
273 + def escape_html(self, text):
274 + return html_escape(text)
275 +
276 ++ def multiline_math(self, text):
277 ++ return text
278 ++
279 + def block_math(self, text):
280 +- return "$$%s$$" % self.escape_html(text)
281 ++ return f"$${self.escape_html(text)}$$"
282 +
283 + def latex_environment(self, name, text):
284 +- name = self.escape_html(name)
285 +- text = self.escape_html(text)
286 +- return rf"\begin{{{name}}}{text}\end{{{name}}}"
287 ++ name, text = self.escape_html(name), self.escape_html(text)
288 ++ return f"\\begin{{{name}}}{text}\\end{{{name}}}"
289 +
290 + def inline_math(self, text):
291 +- return "$%s$" % self.escape_html(text)
292 ++ return f"${self.escape_html(text)}$"
293 +
294 +- def image(self, src, title, text):
295 ++ def image(self, src, text, title):
296 + """Rendering a image with title and text.
297 +
298 + :param src: source link of the image.
299 +- :param title: title text of the image.
300 + :param text: alt text of the image.
301 ++ :param title: title text of the image.
302 + """
303 +- attachments = self.options.get("attachments", {})
304 + attachment_prefix = "attachment:"
305 +- embed_images = self.options.get("embed_images", False)
306 +
307 + if src.startswith(attachment_prefix):
308 + name = src[len(attachment_prefix) :]
309 +
310 +- if name not in attachments:
311 ++ if name not in self.attachments:
312 + raise InvalidNotebook(f"missing attachment: {name}")
313 +
314 +- attachment = attachments[name]
315 ++ attachment = self.attachments[name]
316 + # we choose vector over raster, and lossless over lossy
317 + preferred_mime_types = ["image/svg+xml", "image/png", "image/jpeg"]
318 + for preferred_mime_type in preferred_mime_types:
319 +@@ -197,13 +222,13 @@ class IPythonRenderer(mistune.Renderer):
320 + data = attachment[mime_type]
321 + src = "data:" + mime_type + ";base64," + data
322 +
323 +- elif embed_images:
324 ++ elif self.embed_images:
325 + base64_url = self._src_to_base64(src)
326 +
327 + if base64_url is not None:
328 + src = base64_url
329 +
330 +- return super().image(src, title, text)
331 ++ return super().image(src, text, title)
332 +
333 + def _src_to_base64(self, src):
334 + """Turn the source file into a base64 url.
335 +@@ -211,8 +236,7 @@ class IPythonRenderer(mistune.Renderer):
336 + :param src: source link of the file.
337 + :return: the base64 url or None if the file was not found.
338 + """
339 +- path = self.options.get("path", "")
340 +- src_path = os.path.join(path, src)
341 ++ src_path = os.path.join(self.path, src)
342 +
343 + if not os.path.exists(src_path):
344 + return None
345 +diff --git a/setup.py b/setup.py
346 +index 7220a875..2dfa2534 100644
347 +--- a/setup.py
348 ++++ b/setup.py
349 +@@ -245,7 +245,7 @@ setup_args["install_requires"] = [
350 + "jupyter_core>=4.7",
351 + "jupyterlab_pygments",
352 + "MarkupSafe>=2.0",
353 +- "mistune>=0.8.1,<2",
354 ++ "mistune>=2.0.2",
355 + "nbclient>=0.5.0",
356 + "nbformat>=5.1",
357 + "packaging",
358 +--
359 +2.35.1
360 +
361
362 diff --git a/dev-python/nbconvert/nbconvert-6.5.0-r1.ebuild b/dev-python/nbconvert/nbconvert-6.5.0-r1.ebuild
363 new file mode 100644
364 index 000000000000..39c667a2c576
365 --- /dev/null
366 +++ b/dev-python/nbconvert/nbconvert-6.5.0-r1.ebuild
367 @@ -0,0 +1,82 @@
368 +# Copyright 1999-2022 Gentoo Authors
369 +# Distributed under the terms of the GNU General Public License v2
370 +
371 +EAPI=8
372 +
373 +DISTUTILS_USE_PEP517=setuptools
374 +PYTHON_COMPAT=( python3_{8..10} )
375 +
376 +inherit distutils-r1
377 +
378 +DESCRIPTION="Converting Jupyter Notebooks"
379 +HOMEPAGE="
380 + https://nbconvert.readthedocs.io/
381 + https://github.com/jupyter/nbconvert/
382 + https://pypi.org/project/nbconvert/
383 +"
384 +SRC_URI="
385 + mirror://pypi/${PN:0:1}/${PN}/${P}.tar.gz
386 +"
387 +
388 +LICENSE="BSD"
389 +SLOT="0"
390 +KEYWORDS="~amd64 ~arm ~arm64 ~hppa ~ia64 ~ppc ~ppc64 ~riscv ~s390 ~sparc ~x86"
391 +
392 +RDEPEND="
393 + dev-python/beautifulsoup4[${PYTHON_USEDEP}]
394 + dev-python/bleach[${PYTHON_USEDEP}]
395 + dev-python/defusedxml[${PYTHON_USEDEP}]
396 + >=dev-python/entrypoints-0.2.2[${PYTHON_USEDEP}]
397 + dev-python/jinja[${PYTHON_USEDEP}]
398 + dev-python/jupyter_core[${PYTHON_USEDEP}]
399 + dev-python/jupyterlab_pygments[${PYTHON_USEDEP}]
400 + >=dev-python/markupsafe-2.0[${PYTHON_USEDEP}]
401 + >=dev-python/mistune-2.0.2[${PYTHON_USEDEP}]
402 + dev-python/nbclient[${PYTHON_USEDEP}]
403 + dev-python/nbformat[${PYTHON_USEDEP}]
404 + >=dev-python/pandocfilters-1.4.1[${PYTHON_USEDEP}]
405 + dev-python/pygments[${PYTHON_USEDEP}]
406 + >=dev-python/traitlets-5.1.1[${PYTHON_USEDEP}]
407 + dev-python/testpath[${PYTHON_USEDEP}]
408 + www-servers/tornado[${PYTHON_USEDEP}]
409 +"
410 +BDEPEND="
411 + test? (
412 + dev-python/pebble[${PYTHON_USEDEP}]
413 + dev-python/ipykernel[${PYTHON_USEDEP}]
414 + dev-python/ipywidgets[${PYTHON_USEDEP}]
415 + >=dev-python/jupyter_client-4.2[${PYTHON_USEDEP}]
416 + )
417 +"
418 +
419 +distutils_enable_tests pytest
420 +
421 +PATCHES=(
422 + "${FILESDIR}"/${P}-mistune-2.patch
423 +)
424 +
425 +src_test() {
426 + mkdir -p "${HOME}/.local" || die
427 + cp -r share "${HOME}/.local/" || die
428 + distutils-r1_src_test
429 +}
430 +
431 +python_test() {
432 + local EPYTEST_DESELECT=(
433 + # Missing pyppeteer for now
434 + # TODO: Doesn't skip?
435 + nbconvert/exporters/tests/test_webpdf.py
436 + # Needs pyppeteer too
437 + 'nbconvert/tests/test_nbconvertapp.py::TestNbConvertApp::test_webpdf_with_chromium'
438 + )
439 +
440 + epytest --pyargs nbconvert
441 +}
442 +
443 +pkg_postinst() {
444 + if ! has_version app-text/pandoc ; then
445 + einfo "Pandoc is required for converting to formats other than Python,"
446 + einfo "HTML, and Markdown. If you need this functionality, install"
447 + einfo "app-text/pandoc."
448 + fi
449 +}