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 |
+} |