From 20eb8ac7fded0220278e8818a92968328f9014eb Mon Sep 17 00:00:00 2001 From: Kolen Cheung Date: Thu, 9 Dec 2021 20:36:56 -0800 Subject: ipynb writer: handle cell output with raw block of markdown (#7563) Write RawBlock of markdown in code-cell output. #7561 makes the ipynb reader reads code-cell output with mime "text/markdown" to a RawBlock of markdown This commit makes the ipynb writer writes this RawBlock of markdown back inside a code-cell output with the same mime, preserving this information in round-trip Add tests of ipynb reader (#7561) and ipynb writer (#7563)'s ability to handle a "text/markdown" mime type in a code-cell output --- src/Text/Pandoc/Writers/Ipynb.hs | 2 + test/Tests/Old.hs | 6 ++ test/ipynb/mime.ipynb | 187 +++++++++++++++++++++++++++++++++++++++ test/ipynb/mime.native | 154 ++++++++++++++++++++++++++++++++ test/ipynb/mime.out.ipynb | 169 +++++++++++++++++++++++++++++++++++ 5 files changed, 518 insertions(+) create mode 100644 test/ipynb/mime.ipynb create mode 100644 test/ipynb/mime.native create mode 100644 test/ipynb/mime.out.ipynb diff --git a/src/Text/Pandoc/Writers/Ipynb.hs b/src/Text/Pandoc/Writers/Ipynb.hs index 65299a4c9..47c6e6966 100644 --- a/src/Text/Pandoc/Writers/Ipynb.hs +++ b/src/Text/Pandoc/Writers/Ipynb.hs @@ -253,6 +253,8 @@ extractData bs = do return (M.insert "text/html" (TextualData raw) mmap, meta) go (mmap, meta) (RawBlock (Format "latex") raw) = return (M.insert "text/latex" (TextualData raw) mmap, meta) + go (mmap, meta) (RawBlock (Format "markdown") raw) = + return (M.insert "text/markdown" (TextualData raw) mmap, meta) go (mmap, meta) (Div _ bs') = foldM go (mmap, meta) bs' go (mmap, meta) b = (mmap, meta) <$ report (BlockNotRendered b) diff --git a/test/Tests/Old.hs b/test/Tests/Old.hs index d080f68f1..1d5b0b04e 100644 --- a/test/Tests/Old.hs +++ b/test/Tests/Old.hs @@ -219,6 +219,12 @@ tests pandocPath = "--markdown-headings=setext", "-t", "ipynb-raw_html-raw_tex+raw_attribute", "-s"] "ipynb/simple.in.native" "ipynb/simple.ipynb" + , test' "reader" ["-t", "native", "-f", "ipynb", + "--ipynb-output=all"] + "ipynb/mime.ipynb" "ipynb/mime.native" + , test' "writer" ["-f", "native", "-t", "ipynb", + "--wrap=preserve"] + "ipynb/mime.native" "ipynb/mime.out.ipynb" ] ] where diff --git a/test/ipynb/mime.ipynb b/test/ipynb/mime.ipynb new file mode 100644 index 000000000..8789ca857 --- /dev/null +++ b/test/ipynb/mime.ipynb @@ -0,0 +1,187 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "0ad1fbe7-107b-4668-ae4d-8ce4ae9a4400", + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "from dataclasses import dataclass" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "c2d3a9f4-dfdb-4ced-bbcd-3dfd1780af80", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "7.29.0\n" + ] + } + ], + "source": [ + "import IPython\n", + "\n", + "print(IPython.__version__)" + ] + }, + { + "cell_type": "markdown", + "id": "21e7a4a1-0cf8-48cc-823c-dca698ae6853", + "metadata": {}, + "source": [ + "Supported IPython display formatters:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "053cdbc4-b157-4e3e-9c86-8f374770d006", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "text/plain\n", + "text/html\n", + "text/markdown\n", + "image/svg+xml\n", + "image/png\n", + "application/pdf\n", + "image/jpeg\n", + "text/latex\n", + "application/json\n", + "application/javascript\n" + ] + } + ], + "source": [ + "ip = get_ipython()\n", + "for mime in ip.display_formatter.formatters:\n", + " print(mime)" + ] + }, + { + "cell_type": "markdown", + "id": "d79b063d-ce81-497b-a0ea-5b2e2972e845", + "metadata": {}, + "source": [ + "Let's write a simple class that will output different mime:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c847636c-1c45-432e-9d8d-7310dd7f5637", + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class Mime:\n", + " math: str\n", + "\n", + " def _repr_mimebundle_(\n", + " self,\n", + " include: Container[str] | None = None,\n", + " exclude: Container[str] | None = None,\n", + " **kwargs,\n", + " ) -> dict[str, str]:\n", + " string = self.math\n", + " data = {\n", + " \"text/plain\": string,\n", + " \"text/html\": (latex := f\"\\\\[{string}\\\\]\"),\n", + " \"text/markdown\": f\"$${string}$$\",\n", + " # \"image/svg+xml\":,\n", + " # \"image/png\":,\n", + " # \"application/pdf\":,\n", + " # \"image/jpeg\":,\n", + " \"text/latex\": latex,\n", + " # \"application/json\":,\n", + " # \"application/javascript\":,\n", + " }\n", + " if include:\n", + " data = {k: v for k, v in data.items() if k in include}\n", + " if exclude:\n", + " data = {k: v for k, v in data.items() if k not in exclude}\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "4fa54f22-0c3a-4809-91f7-ea7101ff1907", + "metadata": {}, + "outputs": [], + "source": [ + "mime = Mime(\"E = mc^2\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c419e6a6-240c-4af0-a244-5f1526705c30", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\\[E = mc^2\\]" + ], + "text/latex": [ + "\\[E = mc^2\\]" + ], + "text/markdown": [ + "$$E = mc^2$$" + ], + "text/plain": [ + "E = mc^2" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mime" + ] + }, + { + "cell_type": "markdown", + "id": "bf140b8e-16ac-4670-9778-f1c1d9486f9d", + "metadata": {}, + "source": [ + "Note that #7561 made ipynb reader aware of this, and #7563 made ipynb writer aware of this." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/test/ipynb/mime.native b/test/ipynb/mime.native new file mode 100644 index 000000000..21e3444e2 --- /dev/null +++ b/test/ipynb/mime.native @@ -0,0 +1,154 @@ +[ Div + ( "0ad1fbe7-107b-4668-ae4d-8ce4ae9a4400" + , [ "cell" , "code" ] + , [ ( "execution_count" , "1" ) ] + ) + [ CodeBlock + ( "" , [ "python" ] , [] ) + "from __future__ import annotations\n\nfrom dataclasses import dataclass" + ] +, Div + ( "c2d3a9f4-dfdb-4ced-bbcd-3dfd1780af80" + , [ "cell" , "code" ] + , [ ( "execution_count" , "2" ) ] + ) + [ CodeBlock + ( "" , [ "python" ] , [] ) + "import IPython\n\nprint(IPython.__version__)" + , Div + ( "" , [ "output" , "stream" , "stdout" ] , [] ) + [ CodeBlock ( "" , [] , [] ) "7.29.0\n" ] + ] +, Div + ( "21e7a4a1-0cf8-48cc-823c-dca698ae6853" + , [ "cell" , "markdown" ] + , [] + ) + [ Para + [ Str "Supported" + , Space + , Str "IPython" + , Space + , Str "display" + , Space + , Str "formatters:" + ] + ] +, Div + ( "053cdbc4-b157-4e3e-9c86-8f374770d006" + , [ "cell" , "code" ] + , [ ( "execution_count" , "3" ) ] + ) + [ CodeBlock + ( "" , [ "python" ] , [] ) + "ip = get_ipython()\nfor mime in ip.display_formatter.formatters:\n print(mime)" + , Div + ( "" , [ "output" , "stream" , "stdout" ] , [] ) + [ CodeBlock + ( "" , [] , [] ) + "text/plain\ntext/html\ntext/markdown\nimage/svg+xml\nimage/png\napplication/pdf\nimage/jpeg\ntext/latex\napplication/json\napplication/javascript\n" + ] + ] +, Div + ( "d79b063d-ce81-497b-a0ea-5b2e2972e845" + , [ "cell" , "markdown" ] + , [] + ) + [ Para + [ Str "Let's" + , Space + , Str "write" + , Space + , Str "a" + , Space + , Str "simple" + , Space + , Str "class" + , Space + , Str "that" + , Space + , Str "will" + , Space + , Str "output" + , Space + , Str "different" + , Space + , Str "mime:" + ] + ] +, Div + ( "c847636c-1c45-432e-9d8d-7310dd7f5637" + , [ "cell" , "code" ] + , [ ( "execution_count" , "4" ) ] + ) + [ CodeBlock + ( "" , [ "python" ] , [] ) + "@dataclass\nclass Mime:\n math: str\n\n def _repr_mimebundle_(\n self,\n include: Container[str] | None = None,\n exclude: Container[str] | None = None,\n **kwargs,\n ) -> dict[str, str]:\n string = self.math\n data = {\n \"text/plain\": string,\n \"text/html\": (latex := f\"\\\\[{string}\\\\]\"),\n \"text/markdown\": f\"$${string}$$\",\n # \"image/svg+xml\":,\n # \"image/png\":,\n # \"application/pdf\":,\n # \"image/jpeg\":,\n \"text/latex\": latex,\n # \"application/json\":,\n # \"application/javascript\":,\n }\n if include:\n data = {k: v for k, v in data.items() if k in include}\n if exclude:\n data = {k: v for k, v in data.items() if k not in exclude}\n return data" + ] +, Div + ( "4fa54f22-0c3a-4809-91f7-ea7101ff1907" + , [ "cell" , "code" ] + , [ ( "execution_count" , "5" ) ] + ) + [ CodeBlock + ( "" , [ "python" ] , [] ) "mime = Mime(\"E = mc^2\")" + ] +, Div + ( "c419e6a6-240c-4af0-a244-5f1526705c30" + , [ "cell" , "code" ] + , [ ( "execution_count" , "6" ) ] + ) + [ CodeBlock ( "" , [ "python" ] , [] ) "mime" + , Div + ( "" + , [ "output" , "execute_result" ] + , [ ( "execution_count" , "6" ) ] + ) + [ RawBlock (Format "html") "\\[E = mc^2\\]" + , RawBlock (Format "latex") "\\[E = mc^2\\]" + , RawBlock (Format "markdown") "$$E = mc^2$$" + , CodeBlock ( "" , [] , [] ) "E = mc^2" + ] + ] +, Div + ( "bf140b8e-16ac-4670-9778-f1c1d9486f9d" + , [ "cell" , "markdown" ] + , [] + ) + [ Para + [ Str "Note" + , Space + , Str "that" + , Space + , Str "#7561" + , Space + , Str "made" + , Space + , Str "ipynb" + , Space + , Str "reader" + , Space + , Str "aware" + , Space + , Str "of" + , Space + , Str "this," + , Space + , Str "and" + , Space + , Str "#7563" + , Space + , Str "made" + , Space + , Str "ipynb" + , Space + , Str "writer" + , Space + , Str "aware" + , Space + , Str "of" + , Space + , Str "this." + ] + ] +] diff --git a/test/ipynb/mime.out.ipynb b/test/ipynb/mime.out.ipynb new file mode 100644 index 000000000..4cc806a1e --- /dev/null +++ b/test/ipynb/mime.out.ipynb @@ -0,0 +1,169 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "from dataclasses import dataclass" + ], + "id": "0ad1fbe7-107b-4668-ae4d-8ce4ae9a4400" + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "7.29.0\n" + ] + } + ], + "source": [ + "import IPython\n", + "\n", + "print(IPython.__version__)" + ], + "id": "c2d3a9f4-dfdb-4ced-bbcd-3dfd1780af80" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Supported IPython display formatters:" + ], + "id": "21e7a4a1-0cf8-48cc-823c-dca698ae6853" + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "text/plain\n", + "text/html\n", + "text/markdown\n", + "image/svg+xml\n", + "image/png\n", + "application/pdf\n", + "image/jpeg\n", + "text/latex\n", + "application/json\n", + "application/javascript\n" + ] + } + ], + "source": [ + "ip = get_ipython()\n", + "for mime in ip.display_formatter.formatters:\n", + " print(mime)" + ], + "id": "053cdbc4-b157-4e3e-9c86-8f374770d006" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's write a simple class that will output different mime:" + ], + "id": "d79b063d-ce81-497b-a0ea-5b2e2972e845" + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "@dataclass\n", + "class Mime:\n", + " math: str\n", + "\n", + " def _repr_mimebundle_(\n", + " self,\n", + " include: Container[str] | None = None,\n", + " exclude: Container[str] | None = None,\n", + " **kwargs,\n", + " ) -> dict[str, str]:\n", + " string = self.math\n", + " data = {\n", + " \"text/plain\": string,\n", + " \"text/html\": (latex := f\"\\\\[{string}\\\\]\"),\n", + " \"text/markdown\": f\"$${string}$$\",\n", + " # \"image/svg+xml\":,\n", + " # \"image/png\":,\n", + " # \"application/pdf\":,\n", + " # \"image/jpeg\":,\n", + " \"text/latex\": latex,\n", + " # \"application/json\":,\n", + " # \"application/javascript\":,\n", + " }\n", + " if include:\n", + " data = {k: v for k, v in data.items() if k in include}\n", + " if exclude:\n", + " data = {k: v for k, v in data.items() if k not in exclude}\n", + " return data" + ], + "id": "c847636c-1c45-432e-9d8d-7310dd7f5637" + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "mime = Mime(\"E = mc^2\")" + ], + "id": "4fa54f22-0c3a-4809-91f7-ea7101ff1907" + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "output_type": "execute_result", + "execution_count": 6, + "metadata": {}, + "data": { + "text/html": [ + "\\[E = mc^2\\]" + ], + "text/latex": [ + "\\[E = mc^2\\]" + ], + "text/markdown": [ + "$$E = mc^2$$" + ], + "text/plain": [ + "E = mc^2" + ] + } + } + ], + "source": [ + "mime" + ], + "id": "c419e6a6-240c-4af0-a244-5f1526705c30" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that #7561 made ipynb reader aware of this, and #7563 made ipynb writer aware of this." + ], + "id": "bf140b8e-16ac-4670-9778-f1c1d9486f9d" + } + ], + "nbformat": 4, + "nbformat_minor": 5, + "metadata": {} +} -- cgit v1.2.3