From e159e142ececd6926f9450e39920f6b22eeb7c2a Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 2 Nov 2017 16:16:35 +0100 Subject: [PATCH 01/33] update .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index d5c63d4..946fb8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .coverage +.cache __pycache__ experimental wiki @@ -12,3 +13,7 @@ images build/ dist/ .eggs/ +.idea +pytest.ini +venv +sandbox From fd588b502548bdf15ad15a7b9b206d41fd24999e Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 2 Nov 2017 16:19:34 +0100 Subject: [PATCH 02/33] Create python virtualenv with jupyter. The jupyter virtualenv uses the ContentsManager by default, which facilitates testing. --- .jupyter/jupyter_notebook_config.py | 526 ++++++++++++++++++++++++++++ Makefile | 42 ++- requirements-dev.txt | 1 + 3 files changed, 564 insertions(+), 5 deletions(-) create mode 100644 .jupyter/jupyter_notebook_config.py diff --git a/.jupyter/jupyter_notebook_config.py b/.jupyter/jupyter_notebook_config.py new file mode 100644 index 0000000..af08e00 --- /dev/null +++ b/.jupyter/jupyter_notebook_config.py @@ -0,0 +1,526 @@ +#--- nbextensions configuration --- +import sys +#--- nbextensions configuration --- +# Configuration file for jupyter-notebook. + +#------------------------------------------------------------------------------ +# Configurable configuration +#------------------------------------------------------------------------------ + +#------------------------------------------------------------------------------ +# SingletonConfigurable configuration +#------------------------------------------------------------------------------ + +# A configurable that only allows one instance. +# +# This class is for classes that should only have one instance of itself or +# *any* subclass. To create and retrieve such a class use the +# :meth:`SingletonConfigurable.instance` method. + +#------------------------------------------------------------------------------ +# Application configuration +#------------------------------------------------------------------------------ + +# This is an application. + +# Set the log level by value or name. +# c.Application.log_level = 30 + +# The date format used by logging formatters for %(asctime)s +# c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S' + +# The Logging format template +# c.Application.log_format = '[%(name)s]%(highlevel)s %(message)s' + +#------------------------------------------------------------------------------ +# JupyterApp configuration +#------------------------------------------------------------------------------ + +# Base class for Jupyter applications + +# Full path of a config file. +# c.JupyterApp.config_file = '' + +# Generate default config file. +# c.JupyterApp.generate_config = False + +# Answer yes to any prompts. +# c.JupyterApp.answer_yes = False + +# Specify a config file to load. +# c.JupyterApp.config_file_name = '' + +#------------------------------------------------------------------------------ +# NotebookApp configuration +#------------------------------------------------------------------------------ + +# Use a regular expression for the Access-Control-Allow-Origin header +# +# Requests from an origin matching the expression will get replies with: +# +# Access-Control-Allow-Origin: origin +# +# where `origin` is the origin of the request. +# +# Ignored if allow_origin is set. +# c.NotebookApp.allow_origin_pat = '' + +# Set the Access-Control-Allow-Credentials: true header +# c.NotebookApp.allow_credentials = False + +# Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded- +# For headerssent by the upstream reverse proxy. Necessary if the proxy handles +# SSL +# c.NotebookApp.trust_xheaders = False + +# Reraise exceptions encountered loading server extensions? +# c.NotebookApp.reraise_server_extension_failures = False + +# Hashed password to use for web authentication. +# +# To generate, type in a python/IPython shell: +# +# from notebook.auth import passwd; passwd() +# +# The string should be of the form type:salt:hashed-password. +# c.NotebookApp.password = '' + +# Specify what command to use to invoke a web browser when opening the notebook. +# If not specified, the default browser will be determined by the `webbrowser` +# standard library module, which allows setting of the BROWSER environment +# variable to override it. +# c.NotebookApp.browser = '' + +# DEPRECATED use base_url +# c.NotebookApp.base_project_url = '/' + +# The port the notebook server will listen on. +# c.NotebookApp.port = 8888 + +# The base URL for the notebook server. +# +# Leading and trailing slashes can be omitted, and will automatically be added. +# c.NotebookApp.base_url = '/' + +# Supply extra arguments that will be passed to Jinja environment. +# c.NotebookApp.jinja_environment_options = traitlets.Undefined + +# Supply overrides for the tornado.web.Application that the IPython notebook +# uses. +# c.NotebookApp.tornado_settings = traitlets.Undefined + +# The random bytes used to secure cookies. By default this is a new random +# number every time you start the Notebook. Set it to a value in a config file +# to enable logins to persist across server sessions. +# +# Note: Cookie secrets should be kept private, do not share config files with +# cookie_secret stored in plaintext (you can read the value from a file). +# c.NotebookApp.cookie_secret = b'' + +# The url for MathJax.js. +# c.NotebookApp.mathjax_url = '' + +# extra paths to look for Javascript notebook extensions +# c.NotebookApp.extra_nbextensions_path = traitlets.Undefined + +# The session manager class to use. +# c.NotebookApp.session_manager_class = + +# The full path to an SSL/TLS certificate file. +# c.NotebookApp.certfile = '' + +# Python modules to load as notebook server extensions. This is an experimental +# API, and may change in future releases. +# c.NotebookApp.server_extensions = traitlets.Undefined + +# The kernel manager class to use. +# c.NotebookApp.kernel_manager_class = + +# The IP address the notebook server will listen on. +# c.NotebookApp.ip = 'localhost' + +# DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib. +# c.NotebookApp.pylab = 'disabled' + +# The full path to a private key file for usage with SSL/TLS. +# c.NotebookApp.keyfile = '' + +# DEPRECATED, use tornado_settings +# c.NotebookApp.webapp_settings = traitlets.Undefined + +# Set the Access-Control-Allow-Origin header +# +# Use '*' to allow any origin to access your server. +# +# Takes precedence over allow_origin_pat. +# c.NotebookApp.allow_origin = '' + +# The notebook manager class to use. +# c.NotebookApp.contents_manager_class = + +# The config manager class to use +# c.NotebookApp.config_manager_class = + +# Whether to enable MathJax for typesetting math/TeX +# +# MathJax is the javascript library IPython uses to render math/LaTeX. It is +# very large, so you may want to disable it if you have a slow internet +# connection, or for offline use of the notebook. +# +# When disabled, equations etc. will appear as their untransformed TeX source. +# c.NotebookApp.enable_mathjax = True + +# The kernel spec manager class to use. Should be a subclass of +# `jupyter_client.kernelspec.KernelSpecManager`. +# +# The Api of KernelSpecManager is provisional and might change without warning +# between this version of IPython and the next stable one. +# c.NotebookApp.kernel_spec_manager_class = + +# The number of additional ports to try if the specified port is not available. +# c.NotebookApp.port_retries = 50 + +# Extra paths to search for serving jinja templates. +# +# Can be used to override templates from notebook.templates. +# c.NotebookApp.extra_template_paths = traitlets.Undefined + +# Whether to open in a browser after starting. The specific browser used is +# platform dependent and determined by the python standard library `webbrowser` +# module, unless it is overridden using the --browser (NotebookApp.browser) +# configuration option. +# c.NotebookApp.open_browser = True + +# The default URL to redirect to from `/` +# c.NotebookApp.default_url = '/tree' + +# The file where the cookie secret is stored. +# c.NotebookApp.cookie_secret_file = '' + +# The directory to use for notebooks and kernels. +# c.NotebookApp.notebook_dir = '' + +# Extra paths to search for serving static files. +# +# This allows adding javascript/css to be available from the notebook server +# machine, or overriding individual files in the IPython +# c.NotebookApp.extra_static_paths = traitlets.Undefined + +# The base URL for websockets, if it differs from the HTTP server (hint: it +# almost certainly doesn't). +# +# Should be in the form of an HTTP origin: ws[s]://hostname[:port] +# c.NotebookApp.websocket_url = '' + +# The logout handler class to use. +# c.NotebookApp.logout_handler_class = + +# Extra variables to supply to jinja templates when rendering. +# c.NotebookApp.jinja_template_vars = traitlets.Undefined + +# Supply SSL options for the tornado HTTPServer. See the tornado docs for +# details. +# c.NotebookApp.ssl_options = traitlets.Undefined + +# +# c.NotebookApp.file_to_run = '' + +# The login handler class to use. +# c.NotebookApp.login_handler_class = + +#------------------------------------------------------------------------------ +# LoggingConfigurable configuration +#------------------------------------------------------------------------------ + +# A parent class for Configurables that log. +# +# Subclasses have a log trait, and the default behavior is to get the logger +# from the currently running Application. + +#------------------------------------------------------------------------------ +# ConnectionFileMixin configuration +#------------------------------------------------------------------------------ + +# Mixin for configurable classes that work with connection files + +# JSON file in which to store connection info [default: kernel-.json] +# +# This file will contain the IP, ports, and authentication key needed to connect +# clients to this kernel. By default, this file will be created in the security +# dir of the current profile, but can be specified by absolute path. +# c.ConnectionFileMixin.connection_file = '' + +# set the iopub (PUB) port [default: random] +# c.ConnectionFileMixin.iopub_port = 0 + +# set the control (ROUTER) port [default: random] +# c.ConnectionFileMixin.control_port = 0 + +# set the stdin (ROUTER) port [default: random] +# c.ConnectionFileMixin.stdin_port = 0 + +# set the heartbeat port [default: random] +# c.ConnectionFileMixin.hb_port = 0 + +# set the shell (ROUTER) port [default: random] +# c.ConnectionFileMixin.shell_port = 0 + +# +# c.ConnectionFileMixin.transport = 'tcp' + +# Set the kernel's IP address [default localhost]. If the IP address is +# something other than localhost, then Consoles on other machines will be able +# to connect to the Kernel, so be careful! +# c.ConnectionFileMixin.ip = '' + +#------------------------------------------------------------------------------ +# KernelManager configuration +#------------------------------------------------------------------------------ + +# Manages a single kernel in a subprocess on this host. +# +# This version starts kernels with Popen. + +# DEPRECATED: Use kernel_name instead. +# +# The Popen Command to launch the kernel. Override this if you have a custom +# kernel. If kernel_cmd is specified in a configuration file, Jupyter does not +# pass any arguments to the kernel, because it cannot make any assumptions about +# the arguments that the kernel understands. In particular, this means that the +# kernel does not receive the option --debug if it given on the Jupyter command +# line. +# c.KernelManager.kernel_cmd = traitlets.Undefined + +# Should we autorestart the kernel if it dies. +# c.KernelManager.autorestart = False + +#------------------------------------------------------------------------------ +# Session configuration +#------------------------------------------------------------------------------ + +# Object for handling serialization and sending of messages. +# +# The Session object handles building messages and sending them with ZMQ sockets +# or ZMQStream objects. Objects can communicate with each other over the +# network via Session objects, and only need to work with the dict-based IPython +# message spec. The Session will handle serialization/deserialization, security, +# and metadata. +# +# Sessions support configurable serialization via packer/unpacker traits, and +# signing with HMAC digests via the key/keyfile traits. +# +# Parameters ---------- +# +# debug : bool +# whether to trigger extra debugging statements +# packer/unpacker : str : 'json', 'pickle' or import_string +# importstrings for methods to serialize message parts. If just +# 'json' or 'pickle', predefined JSON and pickle packers will be used. +# Otherwise, the entire importstring must be used. +# +# The functions must accept at least valid JSON input, and output *bytes*. +# +# For example, to use msgpack: +# packer = 'msgpack.packb', unpacker='msgpack.unpackb' +# pack/unpack : callables +# You can also set the pack/unpack callables for serialization directly. +# session : bytes +# the ID of this Session object. The default is to generate a new UUID. +# username : unicode +# username added to message headers. The default is to ask the OS. +# key : bytes +# The key used to initialize an HMAC signature. If unset, messages +# will not be signed or checked. +# keyfile : filepath +# The file containing a key. If this is set, `key` will be initialized +# to the contents of the file. + +# Threshold (in bytes) beyond which a buffer should be sent without copying. +# c.Session.copy_threshold = 65536 + +# The digest scheme used to construct the message signatures. Must have the form +# 'hmac-HASH'. +# c.Session.signature_scheme = 'hmac-sha256' + +# The UUID identifying this session. +# c.Session.session = '' + +# The name of the packer for serializing messages. Should be one of 'json', +# 'pickle', or an import name for a custom callable serializer. +# c.Session.packer = 'json' + +# The maximum number of digests to remember. +# +# The digest history will be culled when it exceeds this value. +# c.Session.digest_history_size = 65536 + +# path to file containing execution key. +# c.Session.keyfile = '' + +# The maximum number of items for a container to be introspected for custom +# serialization. Containers larger than this are pickled outright. +# c.Session.item_threshold = 64 + +# Threshold (in bytes) beyond which an object's buffer should be extracted to +# avoid pickling. +# c.Session.buffer_threshold = 1024 + +# Debug output in the Session +# c.Session.debug = False + +# The name of the unpacker for unserializing messages. Only used with custom +# functions for `packer`. +# c.Session.unpacker = 'json' + +# execution key, for signing messages. +# c.Session.key = b'' + +# Metadata dictionary, which serves as the default top-level metadata dict for +# each message. +# c.Session.metadata = traitlets.Undefined + +# Username for the Session. Default is your system username. +# c.Session.username = 'sturm' + +#------------------------------------------------------------------------------ +# MultiKernelManager configuration +#------------------------------------------------------------------------------ + +# A class for managing multiple kernels. + +# The name of the default kernel to start +# c.MultiKernelManager.default_kernel_name = 'python3' + +# The kernel manager class. This is configurable to allow subclassing of the +# KernelManager for customized behavior. +# c.MultiKernelManager.kernel_manager_class = 'jupyter_client.ioloop.IOLoopKernelManager' + +#------------------------------------------------------------------------------ +# MappingKernelManager configuration +#------------------------------------------------------------------------------ + +# A KernelManager that handles notebook mapping and HTTP error handling + +# +# c.MappingKernelManager.root_dir = '' + +#------------------------------------------------------------------------------ +# ContentsManager configuration +#------------------------------------------------------------------------------ + +# Base class for serving files and directories. +# +# This serves any text or binary file, as well as directories, with special +# handling for JSON notebook documents. +# +# Most APIs take a path argument, which is always an API-style unicode path, and +# always refers to a directory. +# +# - unicode, not url-escaped +# - '/'-separated +# - leading and trailing '/' will be stripped +# - if unspecified, path defaults to '', +# indicating the root path. + +# Python callable or importstring thereof +# +# To be called on a contents model prior to save. +# +# This can be used to process the structure, such as removing notebook outputs +# or other side effects that should not be saved. +# +# It will be called as (all arguments passed by keyword):: +# +# hook(path=path, model=model, contents_manager=self) +# +# - model: the model to be saved. Includes file contents. +# Modifying this dict will affect the file that is stored. +# - path: the API path of the save destination +# - contents_manager: this ContentsManager instance +# c.ContentsManager.pre_save_hook = None + +# +# c.ContentsManager.checkpoints = traitlets.Undefined + +# The base name used when creating untitled notebooks. +# c.ContentsManager.untitled_notebook = 'Untitled' + +# The base name used when creating untitled files. +# c.ContentsManager.untitled_file = 'untitled' + +# +# c.ContentsManager.checkpoints_kwargs = traitlets.Undefined + +# +# c.ContentsManager.checkpoints_class = + +# The base name used when creating untitled directories. +# c.ContentsManager.untitled_directory = 'Untitled Folder' + +# Glob patterns to hide in file and directory listings. +# c.ContentsManager.hide_globs = traitlets.Undefined + +#------------------------------------------------------------------------------ +# FileContentsManager configuration +#------------------------------------------------------------------------------ + +# DEPRECATED, use post_save_hook +# c.FileContentsManager.save_script = False + +# +# c.FileContentsManager.root_dir = '' + +# Python callable or importstring thereof +# +# to be called on the path of a file just saved. +# +# This can be used to process the file on disk, such as converting the notebook +# to a script or HTML via nbconvert. +# +# It will be called as (all arguments passed by keyword):: +# +# hook(os_path=os_path, model=model, contents_manager=instance) +# +# - path: the filesystem path to the file just written - model: the model +# representing the file - contents_manager: this ContentsManager instance +# c.FileContentsManager.post_save_hook = None + +#------------------------------------------------------------------------------ +# NotebookNotary configuration +#------------------------------------------------------------------------------ + +# A class for computing and verifying notebook signatures. + +# The file where the secret key is stored. +# c.NotebookNotary.secret_file = '' + +# The secret key with which notebooks are signed. +# c.NotebookNotary.secret = b'' + +# The number of notebook signatures to cache. When the number of signatures +# exceeds this value, the oldest 25% of signatures will be culled. +# c.NotebookNotary.cache_size = 65535 + +# The hashing algorithm used to sign notebooks. +# c.NotebookNotary.algorithm = 'sha256' + +# The sqlite file in which to store notebook signatures. By default, this will +# be in your Jupyter runtime directory. You can set it to ':memory:' to disable +# sqlite writing to the filesystem. +# c.NotebookNotary.db_file = '' + +#------------------------------------------------------------------------------ +# KernelSpecManager configuration +#------------------------------------------------------------------------------ + +# Whitelist of allowed kernel names. +# +# By default, all installed kernels are allowed. +# c.KernelSpecManager.whitelist = traitlets.Undefined + +c.NotebookApp.server_extensions.append('ipyparallel.nbextension') + +c.NotebookApp.contents_manager_class = 'ipymd.IPymdContentsManager' +c.IPymdContentsManager.format = 'rmarkdown' + +c.NotebookApp.iopub_data_rate_limit = 1e8 diff --git a/Makefile b/Makefile index f487e3c..87152d3 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +VENV=venv/bin +PIP=$(VENV)/pip +PYTHON=$(VENV)/python + help: @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @@ -5,7 +9,7 @@ help: @echo "lint - check style with flake8" @echo "test - run tests quickly with the default Python" -clean: clean-build clean-pyc +clean: clean-build clean-pyc clean-venv clean-build: rm -fr build/ @@ -18,8 +22,36 @@ clean-pyc: find . -name '*~' -exec rm -f {} + find . -name '__pycache__' -exec rm -fr {} + -lint: - flake8 ipymd setup.py --exclude=ipymd/ext/six.py,ipymd/core/contents_manager.py --ignore=E226,E265,F401,F403,F811 +clean-venv: + rm -rf venv/ + +lint: | $(PYTHON) + $(VENV)/flake8 ipymd setup.py --exclude=ipymd/ext/six.py,ipymd/core/contents_manager.py --ignore=E226,E265,F401,F403,F811 + +test: lint | $(PYTHON) + $(PYTHON) setup.py test + +jupyter: | $(PYTHON) + ./venv/bin/jupyter notebook --config=./.jupyter/jupyter_notebook_config.py + +################################################################################ +# Setup python virtual environment +################################################################################ + +.PHONY: python +python: $(PYTHON) + +venv: + virtualenv venv -p /usr/bin/python3 + +$(PIP): | venv + $(PIP) install --upgrade pip + +venv/.installed: requirements-dev.txt | venv + $(PIP) install -Ur requirements-dev.txt + $(PIP) install -e . + echo "pip install successful" > $@ + +$(PYTHON): | $(PIP) venv/.installed + -test: lint - python setup.py test diff --git a/requirements-dev.txt b/requirements-dev.txt index 6aa7fcf..630835b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,3 +4,4 @@ https://github.com/eea/odfpy/archive/master.zip pytest pytest-cov python-coveralls +jupyter From 65b2cd1519d64e931cdab5a17fd92384d14e3548 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 15:23:20 +0100 Subject: [PATCH 03/33] Add new examples files for Rmarkdown. --- examples/ex5.notebook.ipynb | 84 ++++++++++++ examples/ex5.rmarkdown.Rmd | 28 ++++ examples/ex5.rmarkdown.nb.html | 244 +++++++++++++++++++++++++++++++++ examples/ex6.notebook.ipynb | 116 ++++++++++++++++ examples/ex6.rmarkdown.Rmd | 25 ++++ examples/ex6.rmarkdown.nb.html | 238 ++++++++++++++++++++++++++++++++ 6 files changed, 735 insertions(+) create mode 100644 examples/ex5.notebook.ipynb create mode 100644 examples/ex5.rmarkdown.Rmd create mode 100644 examples/ex5.rmarkdown.nb.html create mode 100644 examples/ex6.notebook.ipynb create mode 100644 examples/ex6.rmarkdown.Rmd create mode 100644 examples/ex6.rmarkdown.nb.html diff --git a/examples/ex5.notebook.ipynb b/examples/ex5.notebook.ipynb new file mode 100644 index 0000000..0e4a72a --- /dev/null +++ b/examples/ex5.notebook.ipynb @@ -0,0 +1,84 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Header\n", + "\n", + "A paragraph.\n", + "\n", + "test" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "foo = 'bar'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Python code:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello world!\n" + ] + } + ], + "source": [ + "print(\"Hello world!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "JavaScript code:\n", + "\n", + "```javascript\n", + "console.log(\"Hello world!\");\n", + "```\n", + "\n", + "test" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.6.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/ex5.rmarkdown.Rmd b/examples/ex5.rmarkdown.Rmd new file mode 100644 index 0000000..9efb5de --- /dev/null +++ b/examples/ex5.rmarkdown.Rmd @@ -0,0 +1,28 @@ +--- +output: html_notebook +title: Example 5 +--- + +# Header + +A paragraph. + +test + +```{python} +foo = 'bar' +``` + +Python code: + +```{python} +print("Hello world!") +``` + +JavaScript code: + +```javascript +console.log("Hello world!"); +``` + +test diff --git a/examples/ex5.rmarkdown.nb.html b/examples/ex5.rmarkdown.nb.html new file mode 100644 index 0000000..632cc5f --- /dev/null +++ b/examples/ex5.rmarkdown.nb.html @@ -0,0 +1,244 @@ + + + + + + + + + + + + + +Example 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
LS0tCm91dHB1dDogaHRtbF9ub3RlYm9vawp0aXRsZTogRXhhbXBsZSA1Ci0tLQoKIyBIZWFkZXIKCkEgcGFyYWdyYXBoLgoKdGVzdAoKYGBge3B5dGhvbiB9CmZvbyA9ICdiYXInCmBgYAoKUHl0aG9uIGNvZGU6CgpgYGB7cHl0aG9uIH0KcHJpbnQoIkhlbGxvIHdvcmxkISIpCmBgYAoKSmF2YVNjcmlwdCBjb2RlOgoKYGBgamF2YXNjcmlwdApjb25zb2xlLmxvZygiSGVsbG8gd29ybGQhIik7CmBgYAoKdGVzdAo=
+ + + +
+ + + + + + + + diff --git a/examples/ex6.notebook.ipynb b/examples/ex6.notebook.ipynb new file mode 100644 index 0000000..9e85819 --- /dev/null +++ b/examples/ex6.notebook.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Foo bar kk\n", + "\n", + "$\\sum_{i=1}^n 2^i$" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ä 0\n", + "ä 1\n", + "ä 2\n", + "ä 3\n", + "ä 4\n", + "ä 5\n", + "ä 6\n", + "ä 7\n", + "ä 8\n", + "ä 9\n" + ] + } + ], + "source": [ + "for i in range(10):\n", + " print(\"ä \" + str(i))" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n0MS/AXc8VdgXcacAAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import seaborn as sns\n", + "sns.boxplot(range(10))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.6.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/ex6.rmarkdown.Rmd b/examples/ex6.rmarkdown.Rmd new file mode 100644 index 0000000..b6c1fec --- /dev/null +++ b/examples/ex6.rmarkdown.Rmd @@ -0,0 +1,25 @@ +Foo bar kk + +$\sum_{i=1}^n 2^i$ + +```{python} +for i in range(10): + print("ä " + str(i)) +``` + +```{python} +import seaborn as sns +%matplotlib inline +from pylab import * +``` + +```{python} +import seaborn as sns +sns.boxplot(range(10)) +figure() +plot(range(10)) +``` + +```{python} + +``` diff --git a/examples/ex6.rmarkdown.nb.html b/examples/ex6.rmarkdown.nb.html new file mode 100644 index 0000000..135b586 --- /dev/null +++ b/examples/ex6.rmarkdown.nb.html @@ -0,0 +1,238 @@ + + + + + + + + + + + + + +Example 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
{ base64_rmd }
+ + + +
+ + + + + + + + \ No newline at end of file From 7c6d85599ad61fe1e7b71e0c7eab280d6bb2a78a Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 15:48:26 +0100 Subject: [PATCH 04/33] Add preliminary version of RmarkdownReader and -Writer. --- ipymd/formats/rmarkdown.py | 342 +++++++++++++++++++++ ipymd/formats/tests/_notebook_utils.py | 52 ++++ ipymd/formats/tests/test_markdown.py | 29 ++ ipymd/formats/tests/test_notebook_utils.py | 59 ++++ ipymd/formats/tests/test_rmarkdown.py | 278 +++++++++++++++++ ipymd/lib/markdown.py | 4 +- ipymd/lib/rmarkdown.py | 222 +++++++++++++ ipymd/lib/tests/test_rmarkdown.py | 81 +++++ ipymd/ressources/r_notebook.template.html | 238 ++++++++++++++ ipymd/utils/utils.py | 4 + setup.py | 3 +- 11 files changed, 1309 insertions(+), 3 deletions(-) create mode 100644 ipymd/formats/rmarkdown.py create mode 100644 ipymd/formats/tests/_notebook_utils.py create mode 100644 ipymd/formats/tests/test_notebook_utils.py create mode 100644 ipymd/formats/tests/test_rmarkdown.py create mode 100644 ipymd/lib/rmarkdown.py create mode 100644 ipymd/lib/tests/test_rmarkdown.py create mode 100644 ipymd/ressources/r_notebook.template.html diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py new file mode 100644 index 0000000..5247e6d --- /dev/null +++ b/ipymd/formats/rmarkdown.py @@ -0,0 +1,342 @@ +# -*- coding: utf-8 -*- + + +"""Rmarkdown readers and writers. + +""" + +#------------------------------------------------------------------------------ +# Imports +#------------------------------------------------------------------------------ + +import re +import os.path + +import yaml +import base64 + +try: + import nbformat as nbf + from nbformat.v4.nbbase import validate +except ImportError: + import IPython.nbformat as nbf + from IPython.nbformat.v4.nbbase import validate + +from jinja2 import Environment, PackageLoader, select_autoescape + +from ..utils.utils import _read_text, _write_text, _get_cell_types +from ..core.prompt import create_prompt +from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, _read_rmd_b64, \ + _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, _get_nb_html_path +from .markdown import BaseMarkdownReader, BaseMarkdownWriter + +from ..core.meta import NotebookConsumer, NotebookProducer +from .notebook import IpymdToNotebookWriter + +#------------------------------------------------------------------------------ +# R Markdown +#------------------------------------------------------------------------------ + + +class RmarkdownReader(NotebookProducer): + """ read R notebook format (combine .Rmd and .nb.html) """ + def __init__(self): + self._rmd_reader = RmdReader() + self._html_nb_reader = HtmlNbReader() + + def read(self, contents): + cells_rmd = list(self._rmd_reader.read(contents['rmd'])) + nb_writer = IpymdToNotebookWriter() + for cell in cells_rmd: + nb_writer.write(cell) + nb_rmd = nb_writer.contents + nb_html = self._html_nb_reader.read(contents['html']) + + nb_rmd['cells'] = _merge_consecutive_markdown_cells(nb_rmd['cells']) + nb_html['cells'] = _merge_consecutive_markdown_cells(nb_html['cells']) + + return self._merge_notebooks(nb_rmd, nb_html) + + def _merge_notebooks(self, nb_rmd, nb_html): + # if not consistent, discard output (do not consider html cells) + if self._check_notebook_consistency(nb_rmd['cells'], nb_html['cells']): + for rmd_cell, html_cell in zip(nb_rmd['cells'], nb_html['cells']): + if 'outputs' in html_cell: + rmd_cell['outputs'] = html_cell['outputs'] + return nb_rmd + + def _check_notebook_consistency(self, rmd_cells, html_cells): + """The lists of cells are considered consistent if the cell types appear in the same order. """ + return _get_cell_types(rmd_cells) == _get_cell_types(html_cells) + + +class RmdReader(BaseMarkdownReader): + """Read RMarkdown .Rmd files""" + def __init__(self, prompt=None): + super(RmdReader, self).__init__() + + def read(self, text, rules=None): + raw_cells = super(RmdReader, self).read(text, rules) + cells = [] + + last_index = len(raw_cells) - 1 + + for i, cell in enumerate(raw_cells): + if cell['cell_type'] == 'cell_metadata': + if i + 1 <= last_index: + raw_cells[i + 1].update(metadata=cell['metadata']) + else: + cells.append(cell) + + return _merge_consecutive_markdown_cells(cells) + + # Parser methods + # ------------------------------------------------------------------------- + + def parse_fences(self, m): + lang_meta = m.group(2) + code = m.group(3).rstrip() + + if _is_code_chunk(lang_meta): + cell = self._code_cell(code) + lang, name, meta = _parse_chunk_meta(lang_meta) + cell['lang'] = lang + if name is not None: + cell['name'] = name + if len(meta): + cell['metadata'] = meta + return cell + else: + return self._markdown_cell_from_regex(m) + + def parse_block_code(self, m): + return self._markdown_cell_from_regex(m) + + def parse_block_html(self, m): + return self._markdown_cell_from_regex(m) + + def parse_text(self, m): + return self._markdown_cell_from_regex(m) + + def parse_meta(self, m): + return self._meta_from_regex(m) + + +class HtmlNbReader(object): + """ + Read R noteboook .html.nb files. + + See Also: + - https://github.com/rstudio/rmarkdown/blob/95b8b1fa64f78ca99f225a67fff9817103be568b/R/html_notebook.R + - http://rmarkdown.rstudio.com/r_notebook_format.html + + """ + def_text_or_chunk = re.compile( + r'' + r'([\s\S]+?)' + r'' + ) + + def_chunk_element = re.compile( + r'' + r'([\s\S]+?)' + r'' + ) + + def_image_element = re.compile( + r'' + ) + + def __init__(self): + self._nb = nbf.v4.new_notebook() + self._count = 1 + + def read(self, html): + if html is not None: + for block_type, block_content in self._parse_html(html): + if block_type == 'text': + self._nb['cells'].append(self._text_cell(block_content)) + else: + self._nb['cells'].append(self._chunk_cell(block_content)) + + return self._nb + + def _parse_html(self, html): + """ get a list of rnb-text and rnb-chunks""" + for start_tag, contents, end_tag in self.def_text_or_chunk.findall(html): + assert start_tag == end_tag, "text and chunk blocks must not be nested." + yield start_tag, contents.strip() + + def _parse_image(self, html): + try: + mime, data = next(iter(self.def_image_element.findall(html))) + except StopIteration: + mime = 'text/plain' + data = 'Error reading image.' + return mime, data + + def _text_cell(self, text_block): + # new markdown cell will be filled with html. The corresponding + # source is found in .Rmd file. + return nbf.v4.new_markdown_cell(text_block) + + def _chunk_cell(self, chunk_block): + """parse an rnb-chunk consisting of source/plot/...""" + # A chunk block can be divided in multiple sub-blocks, namely + # + # "source", "plot", "output", + # "warning", "error", "message" + cell = None + + for start_tag, b64, contents, end_tag in self.def_chunk_element.findall(chunk_block): + assert start_tag == end_tag, "different chunk elements must not be nested. " + b64 = b64.strip() + contents = contents.strip() + if start_tag == 'source': + cell = HtmlNbChunkCell(b64, self._count) + elif start_tag in ['output', 'warning', 'error', 'message']: + assert cell is not None, "output without source" + cell.new_output(start_tag, b64) + elif start_tag == 'plot': + assert cell is not None, "plot without source" + mime, data = self._parse_image(contents) + cell.new_plot(mime, data, b64) + + self._count += 1 + return cell.cell + + +class RmarkdownWriter(NotebookConsumer): + """Write R notebook (combine .Rmd and .nb.html) """ + + def __init__(self): + self._rmd_writer = RmdWriter() + self._nb_html_writer = NbHtmlWriter(self._rmd_writer) + + def write_contents(self, nb): + """convert a jupyter notebook dict to rmarkdown""" + assert nb['nbformat'] >= 4 + + self.write_notebook_metadata(nb['metadata']) + for cell in nb['cells']: + self.write(cell) + + def write(self, cell): + self._rmd_writer.write(cell) + self._nb_html_writer.write(cell) + + def write_notebook_metadata(self, metadata): + self._rmd_writer.write_notebook_metadata(metadata) + + def close(self): + self._rmd_writer.close() + + @property + def contents(self): + return { + 'rmd': self._rmd_writer.contents, + 'html': self._nb_html_writer.contents + } + + def __del__(self): + self.close() + + +class NbHtmlWriter(object): + """ Write R notebook .nb.html """ + def __init__(self, rmd_writer): + """ + + Parameters + ---------- + rmd_writer: RmdWriter + a reference to the corresponding RmdWriter, as the rmd output + will also be encoded in the html output. + """ + self._rmd_writer = rmd_writer + + @property + def template(self): + """ Load the jinja2 template from the package resources. """ + env = Environment( + loader=PackageLoader('ipymd', 'ressources'), + autoescape=select_autoescape(['html', 'xml']) + ) + return env.get_template('r_notebook.template.html') + + def write(self, cell): + pass + + @property + def contents(self): + base64_rmd = base64.b64encode(self._rmd_writer.contents.encode()) + return self.template.render(base64_rmd=base64_rmd) + + +class RmdWriter(BaseMarkdownWriter): + """Default .Rmd writer.""" + + def __init__(self): + super(RmdWriter, self).__init__() + + def append_code(self, input, output=None, metadata=None): + code_block = '```{{{meta}}}\n{code}\n```'.format(meta=self._encode_metadata(metadata), code=input.rstrip()) + self._output.write(code_block) + + def _encode_metadata(self, metadata): + def encode_option(key, value): + return "{}={}".format(key, _option_value_str(value)) + + if metadata is not None: + lang = metadata.pop('lang', 'python') # TODO derive default lang from kernel + name = metadata.pop('name', None) + options = ", ".join(encode_option(k, v) for k, v in metadata.items()) + + else: + # TODO tmp workaround + lang = "python" + name = None + options = "" + + out = [lang] + if name is not None: + out.append(" " + name) + if len(options): + out.append(", " + options) + + return "".join(out) + + @property + def contents(self): + return self._output.getvalue().rstrip() + '\n' # end of file \n + + +def load_rmarkdown(path): + """ + Read .Rmd and the corresponding .html.nb + If .html.nb is not available, outputs will be empty. + """ + html_path = _get_nb_html_path(path) + return { + 'rmd': _read_text(path), + 'html': _read_text(html_path) if os.path.isfile(html_path) else None + } + + +def save_rmarkdown(path, contents): + """ + store cells to .Rmd and outputs to .html.nb + """ + html_path = _get_nb_html_path(path) + _write_text(path, contents['rmd']) + # if contents['html'] is not None: + # _write_text(html_path, contents['html']) + + +RMD_FORMAT = dict( + reader=RmarkdownReader, + writer=RmarkdownWriter, + file_extension='.Rmd', + load=load_rmarkdown, + save=save_rmarkdown, +) diff --git a/ipymd/formats/tests/_notebook_utils.py b/ipymd/formats/tests/_notebook_utils.py new file mode 100644 index 0000000..d4ddce0 --- /dev/null +++ b/ipymd/formats/tests/_notebook_utils.py @@ -0,0 +1,52 @@ +"""Test helper functions for comparing ipynb notebooks. """ +from ipymd.utils.utils import _ensure_string + + +def _cell_source(cell): + """Return the input of an ipynb cell.""" + return _ensure_string(cell.get('source', [])) + + +def _cell_outputs(cell): + """Return the output of an ipynb cell.""" + outputs = cell.get('outputs', []) + return outputs + + +def _stream_output_to_result(output): + """Convert a 'stream' output cell to an 'execute_result' cell. """ + if output['output_type'] == 'stream': + return { + 'output_type': 'execute_result', + 'metadata': {}, + 'data': {'text/plain': _ensure_string(output['text']).rstrip()}, + 'execution_count': None + } + else: + return output + + +def _assert_cell_outputs_equal(output_0, output_1, check_metadata=True): + output_0 = _stream_output_to_result(output_0) + output_1 = _stream_output_to_result(output_1) + assert output_0['output_type'] == output_1['output_type'] + assert output_0['data'] == output_1['data'] + assert output_0['execution_count'] == output_1['execution_count'] or \ + output_1['execution_count'] is None or \ + output_0['execution_count'] is None + if check_metadata: + assert output_0['metadata'] == output_1['metadata'] + + +def _assert_cells_equal(cell_0, cell_1, check_metadata=True): + assert cell_0['cell_type'] == cell_1['cell_type'] + assert _cell_source(cell_0) == _cell_source(cell_1) + for output_0, output_1 in zip(_cell_outputs(cell_0), _cell_outputs(cell_1)): + _assert_cell_outputs_equal(output_0, output_1, check_metadata=check_metadata) + + +def _assert_notebooks_equal(nb_0, nb_1, check_notebook_metadata=True, check_cell_metadata=True): + if check_notebook_metadata: + assert nb_0['metadata'] == nb_1['metadata'] + for cell_0, cell_1 in zip(nb_0['cells'], nb_1['cells']): + _assert_cells_equal(cell_0, cell_1, check_metadata=check_cell_metadata) diff --git a/ipymd/formats/tests/test_markdown.py b/ipymd/formats/tests/test_markdown.py index f40ab29..3bfccb4 100644 --- a/ipymd/formats/tests/test_markdown.py +++ b/ipymd/formats/tests/test_markdown.py @@ -10,6 +10,7 @@ from ...utils.utils import _diff, _show_outputs from ._utils import (_test_reader, _test_writer, _exec_test_file, _read_test_file) +from ..markdown import MarkdownReader, MarkdownWriter #------------------------------------------------------------------------------ @@ -87,3 +88,31 @@ def test_decorator(): markdown_bis = convert(cells, to='markdown') assert _diff(markdown, markdown_bis.replace('python', '')) == '' + + +def test_md_notebook_metadata(): + """Test that reading and writing complex notebook metadata results in the identity. """ + mock_metadata = '\n'.join(('---', + 'author: John Doe', + 'date: 2017-05-07', + 'output:', + ' html_document:', + ' - --title-prefix', + ' - Foo', + ' - --id-prefix', + ' - Bar', + ' toc: true', + ' toc_float:', + ' collapsed: false', + ' smooth_scroll: false', + 'title: Habits', + '---', + '')) + + mdreader = MarkdownReader() + cells = mdreader.read(mock_metadata) + + mdwriter = MarkdownWriter() + mdwriter.write_notebook_metadata(cells[0]['metadata']) + + assert mdwriter.contents == mock_metadata diff --git a/ipymd/formats/tests/test_notebook_utils.py b/ipymd/formats/tests/test_notebook_utils.py new file mode 100644 index 0000000..8fa5494 --- /dev/null +++ b/ipymd/formats/tests/test_notebook_utils.py @@ -0,0 +1,59 @@ +"""Test the notebook utils. """ +from ._notebook_utils import _assert_cell_outputs_equal, _assert_notebooks_equal +from ._utils import _read_test_file +import pytest + + +def test_cell_outputs_equal(): + out1 = { + "execution_count": 1, + "output_type": "execute_result", + "metadata": {'output_type': 'output'}, + "data": {'text/plain': "[1] 1 2 3 4 5 6 7 8 9 10"} + } + # execution_count modified + out2 = { + "execution_count": 2, + "output_type": "execute_result", + "metadata": {'output_type': 'output'}, + "data": {'text/plain': "[1] 1 2 3 4 5 6 7 8 9 10"} + } + # execution_count None + out3 = { + "execution_count": None, + "output_type": "execute_result", + "metadata": {'output_type': 'output'}, + "data": {'text/plain': "[1] 1 2 3 4 5 6 7 8 9 10"} + } + # stream + out4 = { + "output_type": "stream", + "name": 'stdout', + "text": "[1] 1 2 3 4 5 6 7 8 9 10" + } + # differing metadata + out5 = { + "execution_count": 1, + "output_type": "execute_result", + "metadata": {'foo': 'bar'}, + "data": {'text/plain': "[1] 1 2 3 4 5 6 7 8 9 10"} + } + _assert_cell_outputs_equal(out1, out1) + with pytest.raises(AssertionError): + _assert_cell_outputs_equal(out1, out2) + _assert_cell_outputs_equal(out1, out3) + _assert_cell_outputs_equal(out1, out4, check_metadata=False) + _assert_cell_outputs_equal(out1, out5, check_metadata=False) + with pytest.raises(AssertionError): + _assert_cell_outputs_equal(out1, out5) + + +def test_assert_notebook_equals(): + ex1 = _read_test_file('ex1', 'notebook') + ex4 = _read_test_file('ex4', 'notebook') + _assert_notebooks_equal(ex1, ex1) + _assert_notebooks_equal(ex4, ex4) + with pytest.raises(AssertionError): + _assert_notebooks_equal(ex1, ex4) + with pytest.raises(AssertionError): + _assert_notebooks_equal(ex4, ex1) diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py new file mode 100644 index 0000000..48e87a5 --- /dev/null +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -0,0 +1,278 @@ +# -*- coding: utf-8 -*- + +"""Test Markdown parser and reader.""" + +# ------------------------------------------------------------------------------ +# Imports +# ------------------------------------------------------------------------------ + +from ipymd.core.format_manager import format_manager, convert +from ipymd.utils.utils import _diff, _show_outputs, _read_text, _ensure_string +from ._utils import (_test_reader, _test_writer, + _exec_test_file, _read_test_file, _test_file_path) +from ._notebook_utils import _assert_notebooks_equal +from ipymd.formats.rmarkdown import * + +from collections import OrderedDict + + +# ------------------------------------------------------------------------------ +# Test Rmarkdown classes and helper functions +# ------------------------------------------------------------------------------ + +def test_htmlnb_parse_html(): + html = """ + + text contents + + + + + """ + htmlnbreader = HtmlNbReader() + result = list(htmlnbreader._parse_html(html)) + assert result == [('text', 'text contents'), ('chunk', '')] + + +def test_htmlnb_parse_image(): + htmlnbreader = HtmlNbReader() + + html = """ +

+ """ + mime, data = list(htmlnbreader._parse_image(html)) + assert mime == 'image/png' + assert data == "iVBORw0KGgoAAAANSUhEUgAAAjwAAAFhCAMAAABDKYAcAAACTFBMVEUAAAABAQEJCQ...==" + + html = """

some other html

""" + mime, data = list(htmlnbreader._parse_image(html)) + assert mime == 'text/plain' + assert data == 'Error reading image.' + + +def test_htmlnb_chunk_cell(): + html = """ + + +
ggplot(data.frame(x=1:10), aes(y=x, x=x)) + geom_point()
+ + +

+ + """ + + htmlnbreader = HtmlNbReader() + assert htmlnbreader._chunk_cell(html) == { + "cell_type": "code", + "execution_count": 1, + "source": HtmlNbChunkCell.NO_CODE_FROM_HTMLNB, + "metadata": {}, + "outputs": [{ + # list of output dicts (described below) + "execution_count": 1, + "output_type": "execute_result", + "metadata": {}, + "data": {'image/png': "iVBORw0KGgoAAAANSUhEUgAAAjwAAAFhCAMAAABDKYAcAAACTFBMVEUAAAABAQEJCQ...=="} + }] + } + + +def test_htmlnb_chunk_cell2(): + html = """ + +
print(1:10)
+ + +
 [1]  1  2  3  4  5  6  7  8  9 10
+ + """ + + htmlnbreader = HtmlNbReader() + assert htmlnbreader._chunk_cell(html) == { + "cell_type": "code", + "execution_count": 1, + "source": HtmlNbChunkCell.NO_CODE_FROM_HTMLNB, + "metadata": {}, + "outputs": [{ + # list of output dicts (described below) + "execution_count": 1, + "output_type": "execute_result", + "metadata": {'output_type': 'output'}, + "data": {'text/plain': "[1] 1 2 3 4 5 6 7 8 9 10"} + }] + } + + +def test_rmd_read_cell_metadata(): + """test that metadata from chunk options is correctly read. """ + chunk1 = \ + """```{r test, str_type="foo", str_single_quote='bar', int_type=42, bool_type=TRUE, null_type=NULL}\n\n```""" + chunk2 = \ + """```{python, str_type="foo", str_single_quote='bar', int_type=42, bool_type=TRUE, null_type=NULL}\n\n```""" + chunk_meta = OrderedDict([('str_type', 'foo'), + ('str_single_quote', 'bar'), + ('int_type', 42), + ('bool_type', True), + ('null_type', None)]) + + rmdreader = RmdReader() + cell1 = rmdreader.read(chunk1)[0] + cell2 = rmdreader.read(chunk2)[0] + assert cell1['lang'] == 'r' + assert cell2['lang'] == 'python' + assert cell1['metadata'] == chunk_meta + assert cell2['metadata'] == chunk_meta + + +def test_rmd_read_notebook_metadata(): + """test that notebook metadata is properly read.""" + rmd = """---\ntitle: "R Notebook"\noutput: html_notebook\n---""" + + rmdreader = RmdReader() + nb_metadata = rmdreader.read(rmd)[0] + assert nb_metadata['metadata'] == { + "title": "R Notebook", + "output": "html_notebook" + } + + +def test_rmarkdown_read_notebook_metadata(): + """test that notebook metadata is properly read.""" + contents = { + "rmd": """---\ntitle: "R Notebook"\noutput: html_notebook\n---""", + "html": None + } + + reader = RmarkdownReader() + nb = reader.read(contents) + assert nb['metadata'] == { + "title": "R Notebook", + "output": "html_notebook" + } + + +def test_rmarkdown_merge_cells(): + """test that source and output are correctly merged. """ + contents = { + "rmd": """```{r}\nprint(1:10)\n```""", + "html": """ + + +
print(1:10)
+ + +
 [1]  1  2  3  4  5  6  7  8  9 10
+ + + """ + } + + reader = RmarkdownReader() + nb = reader.read(contents) + cell = nb['cells'][0] + assert cell['source'] == 'print(1:10)' + assert cell['outputs'] == [{ + "output_type": "execute_result", + 'metadata': {'output_type': 'output'}, + 'data': {"text/plain": "[1] 1 2 3 4 5 6 7 8 9 10"}, + "execution_count": 1 + }] + + +def test_rmarkdown_merge_cells_inconsistent_input(): + """test that source and output are not merged when the sources are inconsistent. """ + contents = { + "rmd": """```{r}\nprint(1:10)\n```\n\n```{r}\nprint(1:10)\n```""", + "html": """ + + +
print(1:10)
+ + +
 [1]  1  2  3  4  5  6  7  8  9 10
+ + + """ + } + + reader = RmarkdownReader() + nb = reader.read(contents) + assert nb['cells'][0]['source'] == nb['cells'][1]['source'] == 'print(1:10)' + assert nb['cells'][0]['outputs'] == nb['cells'][0]['outputs'] == [] + + +def test_rmd_write_cell_metadata(): + """test that cell metadata is properly encoded.""" + expected = """r test, str_type="foo", str_single_quote='bar', int_type=42, bool_type=TRUE, null_type=NULL""" + chunk_meta = OrderedDict([('lang', 'r'), + ('name', 'test'), + ('str_type', 'foo'), + ('str_single_quote', 'bar'), + ('int_type', 42), + ('bool_type', True), + ('null_type', None)]) + + rmdreader = RmdWriter() + result = rmdreader._encode_metadata(chunk_meta) + expected = expected.replace("'", '"') # we know that all strings will be double-quoted. + assert expected == result + +# ------------------------------------------------------------------------------ +# Test Format +# ------------------------------------------------------------------------------ + +def _test_rmarkdown_reader(basename): + """Check that reading Rmarkdown (.Rmd + .nb.html) results in the correct notebook (.ipynb). """ + contents = _read_test_file(basename, 'rmarkdown') + expected = _read_test_file(basename, 'notebook') + converted = convert(contents, from_='rmarkdown') + # TODO check metadata + _assert_notebooks_equal(expected, converted, check_cell_metadata=False, check_notebook_metadata=False) + + +def _test_rmarkdown_writer(basename): + """Check that writing a notebook (.ipynb) to Rmarkdown results in the correct .Rmd + .nb.html files""" + contents = _read_test_file(basename, 'notebook') + expected = _read_test_file(basename, 'rmarkdown') + converted = convert(contents, to_='rmarkdown') + assert converted['rmd'] == expected['rmd'] + assert converted['html'] == expected['html'] + + +def _test_rmarkdown_rmarkdown(basename): + """Check that the double conversion is the identity.""" + + contents = _read_test_file(basename, 'rmarkdown') + notebook = convert(contents, from_='rmarkdown') + converted = convert(notebook, to='rmarkdown') + + assert converted['rmd'] == contents['rmd'] + assert converted['html'] == contents['html'] + + +def test_ex5_reader(): + _test_rmarkdown_reader('ex5') + + +def test_ex5_writer(): + _test_rmarkdown_writer('ex5') + + +def test_ex5_rmarkdown_rmarkdown(): + _test_rmarkdown_rmarkdown('ex5') + + +def test_ex6_reader(): + _test_rmarkdown_reader('ex6') + + +def test_ex6_writer(): + _test_rmarkdown_writer('ex6') + + +def test_ex6_rmarkdown_rmarkdown(): + _test_rmarkdown_rmarkdown('ex6') + + + + diff --git a/ipymd/lib/markdown.py b/ipymd/lib/markdown.py index 2d7ea24..dd2c054 100644 --- a/ipymd/lib/markdown.py +++ b/ipymd/lib/markdown.py @@ -68,7 +68,7 @@ class BlockGrammar(object): newline = re.compile(r'^\n+') block_code = re.compile(r'^( {4}[^\n]+\n*)+') fences = re.compile( - r'^ *(`{3,}|~{3,}) *(\S+)? *\n' # ```lang + r'^ *(`{3,}|~{3,}) *(\S+|\{.+\})? *\n' # ```lang | ```{lang, option1=foo, option2='bar'} r'([\s\S]+?)\s*' r'\1 *(?:\n+|$)' # ``` ) @@ -654,7 +654,7 @@ def _filter_markdown(source, filters): class MarkdownFilter(object): - """Filter Marakdown contents by keeping a subset of the contents. + """Filter Markdown contents by keeping a subset of the contents. Parameters ---------- diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py new file mode 100644 index 0000000..481ed62 --- /dev/null +++ b/ipymd/lib/rmarkdown.py @@ -0,0 +1,222 @@ +import re +from collections import namedtuple, OrderedDict +import base64 +import json +from ipymd.ext.six import string_types + +try: + import nbformat as nbf + from nbformat.v4.nbbase import validate +except ImportError: + import IPython.nbformat as nbf + from IPython.nbformat.v4.nbbase import validate + +Token = namedtuple("Token", ['kind', 'value']) + +# We will accept R and Python literals for boolean values in the markdown document +str_to_literal = { + "NULL": None, + "None": None, + "False": False, + "FALSE": False, + "True": True, + "TRUE": True +} + +literal_to_str = { + True: "TRUE", + False: "FALSE", + None: "NULL" +} + + +def _tokenize_chunk_options(options_line): + """ + Break an options line into a list of tokens. + + Chunk options-line parser. + See *Python Cookbook* 3E, recipie 2.18 + + (c) Tom Augspurger - pystitch + + Parameters + ---------- + options_line : str + + Returns + ------- + tokens : list of tuples + + Notes + ----- + The valid tokens are + * ``KWARG``: an expression line ``foo=bar`` + * ``ARG``: a term like `python`; used for kernel & chunk names + * ``OPEN``: The literal ``{`` + * ``CLOSE``: The literal ``}`` + * ``BLANK``: Whitespace + """ + KWARG = r'(?P([^,=]+ *)= *(".*"|\'.*\'|[^,=}]+))' + ARG = r'(?P\w+)' + OPEN = r'(?P{ *)' + DELIM = r'(?P *, *)' + CLOSE = r'(?P})' + BLANK = r'(?P\s+)' + + master_pat = re.compile('|'.join([KWARG, ARG, OPEN, DELIM, + CLOSE, BLANK])) + + def generate_tokens(pat, text): + scanner = pat.scanner(text) + for m in iter(scanner.match, None): + yield Token(m.lastgroup, m.group(m.lastgroup)) + + tok = list(generate_tokens(master_pat, options_line)) + return tok + + +def _parse_option_value(value): + """Parse a value given as string to the appropriate data type. """ + value = value.strip() + if value in str_to_literal: + # special value + return str_to_literal[value] + elif value.startswith('"') and value.endswith('"'): + # double quoted string + return value.strip('"') + elif value.startswith("'") and value.endswith("'"): + # single quoted string + return value.strip("'") + else: + try: + # Number: int + return int(value) + except ValueError: + try: + # Number: float + return float(value) + except ValueError: + # something else + raise TypeError("Unknown data type in chunk option: {}".format(value)) + + +def _option_value_str(value): + """Convert an option value to the corresponding string""" + try: + return literal_to_str[value] + except KeyError: + # quote a string + if isinstance(value, string_types): + return '"{}"'.format(value) + else: + return str(value) + + +def _process_cell_metadata(kwargs): + """process kwargs such as foo='bar', cat="gold", horse=9, bool_val=TRUE""" + def process_kwarg(kwarg): + key, value = kwarg.split("=") + return key, _parse_option_value(value) + + return OrderedDict([process_kwarg(kwarg) for kwarg in kwargs]) + + +def _is_code_chunk(chunk_lang): + """determine from ```... if the chunk is executable code or documentation code (markdown) """ + return chunk_lang.startswith('{') and chunk_lang.endswith('}') + + +def _parse_chunk_meta(meta_string): + """Process a string in the form {r chunk_name, foo='bar', cat="gold", horse=9, bool_val=TRUE}""" + tokens = _tokenize_chunk_options(meta_string) + args = [] + kwargs = [] + for kind, value in tokens: + if kind == "ARG": + args.append(value) + elif kind == "KWARG": + kwargs.append(value) + + lang = args[0] + name = None if len(args) <= 1 else args[1] + meta = _process_cell_metadata(kwargs) + + return lang, name, meta + + +def _read_rmd_b64(b64): + decoded = base64.b64decode(b64).decode('utf-8') + return json.loads(decoded, encoding='utf-8') + + +def _get_nb_html_path(rmd_path): + assert rmd_path.endswith(".Rmd"), "invalid file extension" + return re.sub(r"\.Rmd$", ".nb.html", rmd_path) + + +def _merge_consecutive_markdown_cells(cells): + """Merge consecutive cells with cell_type == 'markdown'. + + Parameters + ---------- + cells : a list of jupyter notebook cells. + """ + merged = [] + tmp_cell = None + + def done_merging(): + """execute, when switching back from a series of markdown cells to other cell types""" + nonlocal merged, tmp_cell + if tmp_cell is not None: + merged.append(tmp_cell) + tmp_cell = None + + for cell in cells: + if cell['cell_type'] == 'markdown': + if tmp_cell is None: + tmp_cell = cell + else: + if 'source' in cell: + tmp_cell['source'] = tmp_cell.get('source', "") + "\n\n" + cell['source'] + if 'metadata' in cell: + tmp_cell['metadata'] = tmp_cell.get('metadata', {}).update(cell['metadata']) + else: + done_merging() + merged.append(cell) + + done_merging() + + return merged + + +class HtmlNbChunkCell(object): + NO_CODE_FROM_HTMLNB = "Code is not parsed from .html.nb. Use code provided by *.Rmd instead. " + NO_META_FROM_HTMLNB = "Cell metadata is not parsed from .html.nb. Use metadata provided by *.Rmd instead. " + + def __init__(self, b64, execution_count): + self._count = execution_count + fences = _read_rmd_b64(b64)['data'].strip() + self._cell = nbf.v4.new_code_cell(self.NO_CODE_FROM_HTMLNB, + execution_count=self._count) + + def new_output(self, tag, b64): + self._cell.outputs.append( + nbf.v4.new_output('execute_result', + {'text/plain': _read_rmd_b64(b64)['data'].strip()}, + execution_count=self._count, + metadata={"output_type": tag})) + + def new_plot(self, mime, data, b64): + self._cell.outputs.append( + nbf.v4.new_output('execute_result', + {mime: data}, + execution_count=self._count, + metadata=_read_rmd_b64(b64)) + ) + + @property + def cell(self): + return self._cell + + + diff --git a/ipymd/lib/tests/test_rmarkdown.py b/ipymd/lib/tests/test_rmarkdown.py new file mode 100644 index 0000000..07ce487 --- /dev/null +++ b/ipymd/lib/tests/test_rmarkdown.py @@ -0,0 +1,81 @@ +"""Test rmarkdown helper functions. """ + + +from ipymd.lib.rmarkdown import _merge_consecutive_markdown_cells, _read_rmd_b64, _option_value_str, \ + _parse_option_value, _parse_chunk_meta +from collections import OrderedDict + + +def test_merge_consecutive_markdown_cells(): + """Test that consecutive markdown cells are correctly merged. """ + cells = [ + {'cell_type': 'notebook_metadata'}, + {'cell_type': 'markdown', 'source': '1'}, + {'cell_type': 'markdown', 'source': '2', 'x': 'a'}, + {'cell_type': 'markdown'}, + {'cell_type': 'code', 'source': '1'}, + {'cell_type': 'markdown', 'source': '1'}, + {'cell_type': 'markdown'}, + ] + + assert _merge_consecutive_markdown_cells(cells) == [ + {'cell_type': 'notebook_metadata'}, + {'cell_type': 'markdown', 'source': '1\n\n2'}, + {'cell_type': 'code', 'source': '1'}, + {'cell_type': 'markdown', 'source': '1'} + ] + + +def test_merge_consecutive_markdown_cells_2(): + """Test, that consecutive code cells are not merged. """ + cells = [ + {'cell_type': 'notebook_metadata'}, + {'cell_type': 'code', 'source': '1'}, + {'cell_type': 'code', 'source': '2', 'x': 'a'}, + {'cell_type': 'code'}, + {'cell_type': 'code', 'source': '1'}, + {'cell_type': 'markdown', 'source': '1'}, + {'cell_type': 'markdown'}, + ] + + assert _merge_consecutive_markdown_cells(cells) == [ + {'cell_type': 'notebook_metadata'}, + {'cell_type': 'code', 'source': '1'}, + {'cell_type': 'code', 'source': '2', 'x': 'a'}, + {'cell_type': 'code'}, + {'cell_type': 'code', 'source': '1'}, + {'cell_type': 'markdown', 'source': '1'} + ] + + +def test_read_rmd_base64(): + expected = {"data": "```python\nfoo = 'bar'\n```"} + b64 = "eyJkYXRhIjoiYGBgcHl0aG9uXG5mb28gPSAnYmFyJ1xuYGBgIn0=" + assert _read_rmd_b64(b64) == expected + + +def test_parse_chunk_meta(): + chunk_meta = """{r chunk_name, foo='bar', cat="gold", horse=9, bool_val=TRUE}""" + lang, name, meta = _parse_chunk_meta(chunk_meta) + assert lang == 'r' + assert name == 'chunk_name' + assert meta == OrderedDict([ + ('foo', 'bar'), ('cat', 'gold'), ('horse', 9), ('bool_val', True) + ]) + + +def test_option_value_str(): + assert _option_value_str(True) == "TRUE" + assert _option_value_str("foo") == '"foo"' + assert _option_value_str(42) == "42" + assert _option_value_str(None) == "NULL" + + +def test_parse_option_value(): + assert _parse_option_value("'True'") == "True" + assert _parse_option_value('"TRUE"') == "TRUE" + assert _parse_option_value("True") == True + assert _parse_option_value("TRUE") == True + assert type(_parse_option_value("42")) == int + assert type(_parse_option_value("42.")) == float + assert type(_parse_option_value("42")) == int diff --git a/ipymd/ressources/r_notebook.template.html b/ipymd/ressources/r_notebook.template.html new file mode 100644 index 0000000..135b586 --- /dev/null +++ b/ipymd/ressources/r_notebook.template.html @@ -0,0 +1,238 @@ + + + + + + + + + + + + + +Example 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
{ base64_rmd }
+ + + +
+ + + + + + + + \ No newline at end of file diff --git a/ipymd/utils/utils.py b/ipymd/utils/utils.py index d249809..83b3188 100644 --- a/ipymd/utils/utils.py +++ b/ipymd/utils/utils.py @@ -105,6 +105,10 @@ def _show_outputs(*outputs): pprint(output) +def _get_cell_types(cells): + return [cell['cell_type'] for cell in cells] + + #------------------------------------------------------------------------------ # Reading/writing files from/to disk #------------------------------------------------------------------------------ diff --git a/setup.py b/setup.py index 09a4261..33c5c88 100644 --- a/setup.py +++ b/setup.py @@ -77,6 +77,7 @@ def run_tests(self): 'ipymd=ipymd.core.scripts:main', ], 'ipymd.format': [ + 'rmarkdown=ipymd.formats.rmarkdown:RMD_FORMAT', 'markdown=ipymd.formats.markdown:MARKDOWN_FORMAT', 'atlas=ipymd.formats.atlas:ATLAS_FORMAT', 'notebook=ipymd.formats.notebook:NOTEBOOK_FORMAT', @@ -84,7 +85,7 @@ def run_tests(self): 'python=ipymd.formats.python:PYTHON_FORMAT', ] }, - install_requires=['pyyaml'], + install_requires=['pyyaml', 'jinja2'], extras_require={ 'odf': ['odfpy'], }, From eb3cc8af7d76a897dde320411ed769398c2e08bd Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 15:53:34 +0100 Subject: [PATCH 05/33] Add transutils for compatibility with more recent jupyter version. --- ipymd/core/contents_manager.py | 2 ++ ipymd/core/format_manager.py | 1 + 2 files changed, 3 insertions(+) diff --git a/ipymd/core/contents_manager.py b/ipymd/core/contents_manager.py index dda1591..a475681 100644 --- a/ipymd/core/contents_manager.py +++ b/ipymd/core/contents_manager.py @@ -9,6 +9,8 @@ import io import os import os.path as op +import notebook.transutils + from tornado import web diff --git a/ipymd/core/format_manager.py b/ipymd/core/format_manager.py index 66b5a20..540c90e 100644 --- a/ipymd/core/format_manager.py +++ b/ipymd/core/format_manager.py @@ -12,6 +12,7 @@ import os.path as op import glob import json +import notebook.transutils from pkg_resources import iter_entry_points, DistributionNotFound From 2f32a508cdb17b9eac24939bcf05e9502068a9d9 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 16:08:37 +0100 Subject: [PATCH 06/33] update readme. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index e96f004..f131dc9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ [![Coverage Status](https://coveralls.io/repos/rossant/ipymd/badge.svg)](https://coveralls.io/r/rossant/ipymd) # Replace .ipynb with .md in the IPython Notebook +** the goal of this for is to implement the [R Notebook](http://rmarkdown.rstudio.com/r_notebook_format.html) format as a file format for jupyter ** + +This format stores the output in a separate `.nb.html` file, while the code chunks and markdown cells go into a `.Rmd` file. +This has the advantage over pure markdown, that the output can be stored (including images) and the advantage over `.ipynb` that it works well with git and is editable in a text editor. + + +--------- + The goal of ipymd is to replace `.ipynb` notebook files like: From e6b51ea22226febfae70c5419b65577de1b669fe Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 17:03:07 +0100 Subject: [PATCH 07/33] Hook RmarkdownReader/Writer into FormatManager. --- ipymd/core/format_manager.py | 27 ++++++++++++++++++++++ ipymd/formats/rmarkdown.py | 32 +++++++++++++++------------ ipymd/formats/tests/test_rmarkdown.py | 8 +++---- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/ipymd/core/format_manager.py b/ipymd/core/format_manager.py index 540c90e..a6a0fcb 100644 --- a/ipymd/core/format_manager.py +++ b/ipymd/core/format_manager.py @@ -253,6 +253,18 @@ def convert(self, if to_kwargs is None: to_kwargs = {} + if from_ == 'rmarkdown' or to == 'rmarkdown': + # TODO: HACK: do rmarkdown conversion in external function. + # As Rmarkdown uses jupyter nbformat as internal format instead of + # ipymd cells, the usual 'conversion' scheme does not work. + # atm, I cannot think of an elegant solution which does not require + # to change a good deal of the ipymd code. + # + # The jupyter format was chosen as internal format to enable + # more complex outputs (i.e. multiple outputs per cell, including + # multiple images). + return convert_rmarkdown(from_, to, contents) + if reader is None: reader = (self.create_reader(from_, **from_kwargs) if from_ is not None else None) @@ -335,3 +347,18 @@ def format_manager(): def convert(*args, **kwargs): """Alias for format_manager().convert().""" return format_manager().convert(*args, **kwargs) + + +def convert_rmarkdown(from_, to, contents): + from ..formats.rmarkdown import RmarkdownReader, RmarkdownWriter + if to == 'rmarkdown' and from_ == 'notebook': + writer = RmarkdownWriter() + writer.write_contents(contents) + return writer.contents + + elif to == 'notebook' and from_ == 'rmarkdown': + reader = RmarkdownReader() + return reader.read(contents) + + else: + raise RuntimeError("Rmarkdown conversion is only possible between 'notebook' and 'rmarkdown' format. ") diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 5247e6d..abe8123 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -12,7 +12,6 @@ import re import os.path -import yaml import base64 try: @@ -25,37 +24,42 @@ from jinja2 import Environment, PackageLoader, select_autoescape from ..utils.utils import _read_text, _write_text, _get_cell_types -from ..core.prompt import create_prompt -from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, _read_rmd_b64, \ +from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, \ _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, _get_nb_html_path from .markdown import BaseMarkdownReader, BaseMarkdownWriter +from ipymd.core.format_manager import convert -from ..core.meta import NotebookConsumer, NotebookProducer -from .notebook import IpymdToNotebookWriter #------------------------------------------------------------------------------ # R Markdown #------------------------------------------------------------------------------ -class RmarkdownReader(NotebookProducer): - """ read R notebook format (combine .Rmd and .nb.html) """ +class RmarkdownReader(object): + """ read R notebook format (combine .Rmd and .nb.html). + Output format is a jupyter nbformat object. + + Unlike all other ipymd readers, this class uses the jupyter nbformat + as internal representation of the notebook. The jupyter format was chosen as internal format to enable + more complex outputs (i.e. multiple outputs per cell, including + multiple images)""" + def __init__(self): self._rmd_reader = RmdReader() self._html_nb_reader = HtmlNbReader() def read(self, contents): - cells_rmd = list(self._rmd_reader.read(contents['rmd'])) - nb_writer = IpymdToNotebookWriter() - for cell in cells_rmd: - nb_writer.write(cell) - nb_rmd = nb_writer.contents + cells_rmd = self._rmd_reader.read(contents['rmd']) nb_html = self._html_nb_reader.read(contents['html']) + # we need both inputs in jupyter nb format, so we can merge them. + nb_rmd = convert(cells_rmd, to='notebook') nb_rmd['cells'] = _merge_consecutive_markdown_cells(nb_rmd['cells']) nb_html['cells'] = _merge_consecutive_markdown_cells(nb_html['cells']) - return self._merge_notebooks(nb_rmd, nb_html) + nb_merged = self._merge_notebooks(nb_rmd, nb_html) + validate(nb_merged) + return nb_merged def _merge_notebooks(self, nb_rmd, nb_html): # if not consistent, discard output (do not consider html cells) @@ -206,7 +210,7 @@ def _chunk_cell(self, chunk_block): return cell.cell -class RmarkdownWriter(NotebookConsumer): +class RmarkdownWriter(object): """Write R notebook (combine .Rmd and .nb.html) """ def __init__(self): diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index 48e87a5..e62a8f6 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -225,7 +225,7 @@ def _test_rmarkdown_reader(basename): """Check that reading Rmarkdown (.Rmd + .nb.html) results in the correct notebook (.ipynb). """ contents = _read_test_file(basename, 'rmarkdown') expected = _read_test_file(basename, 'notebook') - converted = convert(contents, from_='rmarkdown') + converted = convert(contents, from_='rmarkdown', to='notebook') # TODO check metadata _assert_notebooks_equal(expected, converted, check_cell_metadata=False, check_notebook_metadata=False) @@ -234,7 +234,7 @@ def _test_rmarkdown_writer(basename): """Check that writing a notebook (.ipynb) to Rmarkdown results in the correct .Rmd + .nb.html files""" contents = _read_test_file(basename, 'notebook') expected = _read_test_file(basename, 'rmarkdown') - converted = convert(contents, to_='rmarkdown') + converted = convert(contents, to_='rmarkdown', from_='notebook') assert converted['rmd'] == expected['rmd'] assert converted['html'] == expected['html'] @@ -243,8 +243,8 @@ def _test_rmarkdown_rmarkdown(basename): """Check that the double conversion is the identity.""" contents = _read_test_file(basename, 'rmarkdown') - notebook = convert(contents, from_='rmarkdown') - converted = convert(notebook, to='rmarkdown') + notebook = convert(contents, from_='rmarkdown', to='notebook') + converted = convert(notebook, from_='notebook', to='rmarkdown') assert converted['rmd'] == contents['rmd'] assert converted['html'] == contents['html'] From 3d0c4e9926fa8e863764076b716a4fbd5398be49 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 17:36:11 +0100 Subject: [PATCH 08/33] linting --- ipymd/formats/rmarkdown.py | 37 ++++++++------ ipymd/formats/tests/test_rmarkdown.py | 69 ++++++++++++++------------- ipymd/lib/markdown.py | 3 +- ipymd/lib/rmarkdown.py | 29 ++++++----- ipymd/lib/tests/test_rmarkdown.py | 4 +- 5 files changed, 80 insertions(+), 62 deletions(-) diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index abe8123..621113b 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -5,9 +5,9 @@ """ -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # Imports -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ import re import os.path @@ -25,14 +25,15 @@ from ..utils.utils import _read_text, _write_text, _get_cell_types from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, \ - _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, _get_nb_html_path + _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, \ + _get_nb_html_path from .markdown import BaseMarkdownReader, BaseMarkdownWriter from ipymd.core.format_manager import convert -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ # R Markdown -#------------------------------------------------------------------------------ +# ------------------------------------------------------------------------------ class RmarkdownReader(object): @@ -40,9 +41,9 @@ class RmarkdownReader(object): Output format is a jupyter nbformat object. Unlike all other ipymd readers, this class uses the jupyter nbformat - as internal representation of the notebook. The jupyter format was chosen as internal format to enable - more complex outputs (i.e. multiple outputs per cell, including - multiple images)""" + as internal representation of the notebook. The jupyter format was chosen + as internal format to enable more complex outputs + (i.e. multiple outputs per cell, including multiple images)""" def __init__(self): self._rmd_reader = RmdReader() @@ -70,13 +71,14 @@ def _merge_notebooks(self, nb_rmd, nb_html): return nb_rmd def _check_notebook_consistency(self, rmd_cells, html_cells): - """The lists of cells are considered consistent if the cell types appear in the same order. """ + """The lists of cells are considered consistent if the cell + types appear in the same order. """ return _get_cell_types(rmd_cells) == _get_cell_types(html_cells) class RmdReader(BaseMarkdownReader): """Read RMarkdown .Rmd files""" - def __init__(self, prompt=None): + def __init__(self): super(RmdReader, self).__init__() def read(self, text, rules=None): @@ -168,7 +170,8 @@ def read(self, html): def _parse_html(self, html): """ get a list of rnb-text and rnb-chunks""" for start_tag, contents, end_tag in self.def_text_or_chunk.findall(html): - assert start_tag == end_tag, "text and chunk blocks must not be nested." + assert start_tag == end_tag, \ + "text and chunk blocks must not be nested." yield start_tag, contents.strip() def _parse_image(self, html): @@ -193,7 +196,8 @@ def _chunk_cell(self, chunk_block): cell = None for start_tag, b64, contents, end_tag in self.def_chunk_element.findall(chunk_block): - assert start_tag == end_tag, "different chunk elements must not be nested. " + assert start_tag == end_tag, \ + "different chunk elements must not be nested. " b64 = b64.strip() contents = contents.strip() if start_tag == 'source': @@ -284,7 +288,8 @@ def __init__(self): super(RmdWriter, self).__init__() def append_code(self, input, output=None, metadata=None): - code_block = '```{{{meta}}}\n{code}\n```'.format(meta=self._encode_metadata(metadata), code=input.rstrip()) + code_block = '```{{{meta}}}\n{code}\n```'.format( + meta=self._encode_metadata(metadata), code=input.rstrip()) self._output.write(code_block) def _encode_metadata(self, metadata): @@ -292,9 +297,11 @@ def encode_option(key, value): return "{}={}".format(key, _option_value_str(value)) if metadata is not None: - lang = metadata.pop('lang', 'python') # TODO derive default lang from kernel + # TODO derive default lang from kernel + lang = metadata.pop('lang', 'python') name = metadata.pop('name', None) - options = ", ".join(encode_option(k, v) for k, v in metadata.items()) + options = ", ".join(encode_option(k, v) + for k, v in metadata.items()) else: # TODO tmp workaround diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index e62a8f6..b865b34 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -6,12 +6,11 @@ # Imports # ------------------------------------------------------------------------------ -from ipymd.core.format_manager import format_manager, convert -from ipymd.utils.utils import _diff, _show_outputs, _read_text, _ensure_string -from ._utils import (_test_reader, _test_writer, - _exec_test_file, _read_test_file, _test_file_path) +from ipymd.core.format_manager import convert +from ._utils import _read_test_file from ._notebook_utils import _assert_notebooks_equal -from ipymd.formats.rmarkdown import * +from ipymd.formats.rmarkdown import HtmlNbChunkCell, RmarkdownWriter, \ + RmarkdownReader, RmdWriter, RmdReader, NbHtmlWriter, HtmlNbReader from collections import OrderedDict @@ -26,23 +25,24 @@ def test_htmlnb_parse_html(): text contents - + """ htmlnbreader = HtmlNbReader() result = list(htmlnbreader._parse_html(html)) - assert result == [('text', 'text contents'), ('chunk', '')] + assert result == [('text', 'text contents'), + ('chunk', '')] def test_htmlnb_parse_image(): htmlnbreader = HtmlNbReader() - html = """ -

- """ + html = '

' mime, data = list(htmlnbreader._parse_image(html)) assert mime == 'image/png' - assert data == "iVBORw0KGgoAAAANSUhEUgAAAjwAAAFhCAMAAABDKYAcAAACTFBMVEUAAAABAQEJCQ...==" + assert data == "iVBORw0KGgoAAAANSUhEUgAAAjwAAAFhCAMAAABDKYAcAAACT" \ + "FBMVEUAAAABAQEJCQ...==" html = """

some other html

""" mime, data = list(htmlnbreader._parse_image(html)) @@ -63,17 +63,18 @@ def test_htmlnb_chunk_cell(): htmlnbreader = HtmlNbReader() assert htmlnbreader._chunk_cell(html) == { - "cell_type": "code", - "execution_count": 1, - "source": HtmlNbChunkCell.NO_CODE_FROM_HTMLNB, - "metadata": {}, - "outputs": [{ - # list of output dicts (described below) - "execution_count": 1, - "output_type": "execute_result", - "metadata": {}, - "data": {'image/png': "iVBORw0KGgoAAAANSUhEUgAAAjwAAAFhCAMAAABDKYAcAAACTFBMVEUAAAABAQEJCQ...=="} - }] + "cell_type": "code", + "execution_count": 1, + "source": HtmlNbChunkCell.NO_CODE_FROM_HTMLNB, + "metadata": {}, + "outputs": [{ + # list of output dicts (described below) + "execution_count": 1, + "output_type": "execute_result", + "metadata": {}, + "data": {'image/png': "iVBORw0KGgoAAAANSUhEUgAAAjwAAAFhCAMAAABD" + "KYAcAAACTFBMVEUAAAABAQEJCQ...=="} + }] } @@ -180,7 +181,8 @@ def test_rmarkdown_merge_cells(): def test_rmarkdown_merge_cells_inconsistent_input(): - """test that source and output are not merged when the sources are inconsistent. """ + """test that source and output are not merged when the sources + are inconsistent. """ contents = { "rmd": """```{r}\nprint(1:10)\n```\n\n```{r}\nprint(1:10)\n```""", "html": """ @@ -197,7 +199,8 @@ def test_rmarkdown_merge_cells_inconsistent_input(): reader = RmarkdownReader() nb = reader.read(contents) - assert nb['cells'][0]['source'] == nb['cells'][1]['source'] == 'print(1:10)' + assert (nb['cells'][0]['source'] == nb['cells'][1]['source'] + == 'print(1:10)') assert nb['cells'][0]['outputs'] == nb['cells'][0]['outputs'] == [] @@ -214,24 +217,30 @@ def test_rmd_write_cell_metadata(): rmdreader = RmdWriter() result = rmdreader._encode_metadata(chunk_meta) - expected = expected.replace("'", '"') # we know that all strings will be double-quoted. + # we know that all strings will be double-quoted. + expected = expected.replace("'", '"') assert expected == result + # ------------------------------------------------------------------------------ # Test Format # ------------------------------------------------------------------------------ + def _test_rmarkdown_reader(basename): - """Check that reading Rmarkdown (.Rmd + .nb.html) results in the correct notebook (.ipynb). """ + """Check that reading Rmarkdown (.Rmd + .nb.html) results + in the correct notebook (.ipynb). """ contents = _read_test_file(basename, 'rmarkdown') expected = _read_test_file(basename, 'notebook') converted = convert(contents, from_='rmarkdown', to='notebook') # TODO check metadata - _assert_notebooks_equal(expected, converted, check_cell_metadata=False, check_notebook_metadata=False) + _assert_notebooks_equal(expected, converted, check_cell_metadata=False, + check_notebook_metadata=False) def _test_rmarkdown_writer(basename): - """Check that writing a notebook (.ipynb) to Rmarkdown results in the correct .Rmd + .nb.html files""" + """Check that writing a notebook (.ipynb) to Rmarkdown results + in the correct .Rmd + .nb.html files""" contents = _read_test_file(basename, 'notebook') expected = _read_test_file(basename, 'rmarkdown') converted = convert(contents, to_='rmarkdown', from_='notebook') @@ -272,7 +281,3 @@ def test_ex6_writer(): def test_ex6_rmarkdown_rmarkdown(): _test_rmarkdown_rmarkdown('ex6') - - - - diff --git a/ipymd/lib/markdown.py b/ipymd/lib/markdown.py index dd2c054..113a64d 100644 --- a/ipymd/lib/markdown.py +++ b/ipymd/lib/markdown.py @@ -68,7 +68,8 @@ class BlockGrammar(object): newline = re.compile(r'^\n+') block_code = re.compile(r'^( {4}[^\n]+\n*)+') fences = re.compile( - r'^ *(`{3,}|~{3,}) *(\S+|\{.+\})? *\n' # ```lang | ```{lang, option1=foo, option2='bar'} + # ```lang | ```{lang, option1=foo, option2='bar'} + r'^ *(`{3,}|~{3,}) *(\S+|\{.+\})? *\n' r'([\s\S]+?)\s*' r'\1 *(?:\n+|$)' # ``` ) diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py index 481ed62..a35dd0b 100644 --- a/ipymd/lib/rmarkdown.py +++ b/ipymd/lib/rmarkdown.py @@ -13,7 +13,8 @@ Token = namedtuple("Token", ['kind', 'value']) -# We will accept R and Python literals for boolean values in the markdown document +# We will accept R and Python literals for boolean +# values in the markdown document str_to_literal = { "NULL": None, "None": None, @@ -97,7 +98,8 @@ def _parse_option_value(value): return float(value) except ValueError: # something else - raise TypeError("Unknown data type in chunk option: {}".format(value)) + raise TypeError( + "Unknown data type in chunk option: {}".format(value)) def _option_value_str(value): @@ -122,12 +124,14 @@ def process_kwarg(kwarg): def _is_code_chunk(chunk_lang): - """determine from ```... if the chunk is executable code or documentation code (markdown) """ + """determine from ```... if the chunk is executable code + or documentation code (markdown) """ return chunk_lang.startswith('{') and chunk_lang.endswith('}') def _parse_chunk_meta(meta_string): - """Process a string in the form {r chunk_name, foo='bar', cat="gold", horse=9, bool_val=TRUE}""" + """Process a string in the form + {r chunk_name, foo='bar', cat="gold", horse=9, bool_val=TRUE}""" tokens = _tokenize_chunk_options(meta_string) args = [] kwargs = [] @@ -165,7 +169,8 @@ def _merge_consecutive_markdown_cells(cells): tmp_cell = None def done_merging(): - """execute, when switching back from a series of markdown cells to other cell types""" + """execute, when switching back from a series of markdown + cells to other cell types""" nonlocal merged, tmp_cell if tmp_cell is not None: merged.append(tmp_cell) @@ -190,19 +195,22 @@ def done_merging(): class HtmlNbChunkCell(object): - NO_CODE_FROM_HTMLNB = "Code is not parsed from .html.nb. Use code provided by *.Rmd instead. " - NO_META_FROM_HTMLNB = "Cell metadata is not parsed from .html.nb. Use metadata provided by *.Rmd instead. " + NO_CODE_FROM_HTMLNB = "Code is not parsed from .html.nb. " \ + "Use code provided by *.Rmd instead. " + NO_META_FROM_HTMLNB = "Cell metadata is not parsed from .html.nb. " \ + "Use metadata provided by *.Rmd instead. " def __init__(self, b64, execution_count): self._count = execution_count - fences = _read_rmd_b64(b64)['data'].strip() + # fences = _read_rmd_b64(b64)['data'].strip() self._cell = nbf.v4.new_code_cell(self.NO_CODE_FROM_HTMLNB, execution_count=self._count) def new_output(self, tag, b64): self._cell.outputs.append( nbf.v4.new_output('execute_result', - {'text/plain': _read_rmd_b64(b64)['data'].strip()}, + {'text/plain': + _read_rmd_b64(b64)['data'].strip()}, execution_count=self._count, metadata={"output_type": tag})) @@ -217,6 +225,3 @@ def new_plot(self, mime, data, b64): @property def cell(self): return self._cell - - - diff --git a/ipymd/lib/tests/test_rmarkdown.py b/ipymd/lib/tests/test_rmarkdown.py index 07ce487..a87ae6c 100644 --- a/ipymd/lib/tests/test_rmarkdown.py +++ b/ipymd/lib/tests/test_rmarkdown.py @@ -74,8 +74,8 @@ def test_option_value_str(): def test_parse_option_value(): assert _parse_option_value("'True'") == "True" assert _parse_option_value('"TRUE"') == "TRUE" - assert _parse_option_value("True") == True - assert _parse_option_value("TRUE") == True + assert _parse_option_value("True") + assert _parse_option_value("TRUE") assert type(_parse_option_value("42")) == int assert type(_parse_option_value("42.")) == float assert type(_parse_option_value("42")) == int From 34e8ef9300612c7a50220bfa5a138996baf185ca Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 3 Nov 2017 18:10:29 +0100 Subject: [PATCH 09/33] Move utility function from formats/notebook to lib. Merge these utility functions with testing utilities which were previously located in test-utils. --- ipymd/formats/notebook.py | 39 +------------------ ipymd/formats/tests/test_notebook.py | 9 +++-- ipymd/formats/tests/test_rmarkdown.py | 2 +- .../_notebook_utils.py => lib/notebook.py} | 38 +++++++++++++++++- .../tests/test_notebook_utils.py | 12 +++++- 5 files changed, 54 insertions(+), 46 deletions(-) rename ipymd/{formats/tests/_notebook_utils.py => lib/notebook.py} (60%) rename ipymd/{formats => lib}/tests/test_notebook_utils.py (82%) diff --git a/ipymd/formats/notebook.py b/ipymd/formats/notebook.py index 02ed0ab..142632e 100644 --- a/ipymd/formats/notebook.py +++ b/ipymd/formats/notebook.py @@ -19,44 +19,7 @@ from ..lib.python import PythonFilter from ..ext.six import string_types from ..utils.utils import _ensure_string - - -#------------------------------------------------------------------------------ -# Utility functions -#------------------------------------------------------------------------------ - -def _cell_input(cell): - """Return the input of an ipynb cell.""" - return _ensure_string(cell.get('source', [])) - - -def _cell_output(cell): - """Return the output of an ipynb cell.""" - outputs = cell.get('outputs', []) - # Add stdout. - stdout = ('\n'.join(_ensure_string(output.get('text', '')) - for output in outputs)).rstrip() - # Add text output. - text_outputs = [] - for output in outputs: - out = output.get('data', {}).get('text/plain', []) - out = _ensure_string(out) - # HACK: skip outputs. - if out.startswith(' nb) are the same. """ converted, expected = _test_writer(basename, 'notebook') - assert _compare_notebooks(converted, expected) + + _assert_notebooks_equal(converted, expected, check_notebook_metadata=False) + # assert _compare_notebooks(converted, expected) def _test_notebook_notebook(basename): @@ -37,7 +39,8 @@ def _test_notebook_notebook(basename): cells = convert(contents, from_='notebook') converted = convert(cells, to='notebook') - assert _compare_notebooks(contents, converted) + _assert_notebooks_equal(contents, converted, check_notebook_metadata=False) + # assert _compare_notebooks(contents, converted) def test_notebook_reader(): diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index b865b34..b77cd71 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -8,7 +8,7 @@ from ipymd.core.format_manager import convert from ._utils import _read_test_file -from ._notebook_utils import _assert_notebooks_equal +from ...lib.notebook import _assert_notebooks_equal from ipymd.formats.rmarkdown import HtmlNbChunkCell, RmarkdownWriter, \ RmarkdownReader, RmdWriter, RmdReader, NbHtmlWriter, HtmlNbReader diff --git a/ipymd/formats/tests/_notebook_utils.py b/ipymd/lib/notebook.py similarity index 60% rename from ipymd/formats/tests/_notebook_utils.py rename to ipymd/lib/notebook.py index d4ddce0..de6b381 100644 --- a/ipymd/formats/tests/_notebook_utils.py +++ b/ipymd/lib/notebook.py @@ -26,9 +26,18 @@ def _stream_output_to_result(output): return output +def _output_ensure_string(*args): + """make sure that strings split up as a list are coerced into a + single string. """ + for output in args: + for mime, out in output['data'].items(): + output['data'][mime] = _ensure_string(out) + + def _assert_cell_outputs_equal(output_0, output_1, check_metadata=True): output_0 = _stream_output_to_result(output_0) output_1 = _stream_output_to_result(output_1) + _output_ensure_string(output_0, output_1) assert output_0['output_type'] == output_1['output_type'] assert output_0['data'] == output_1['data'] assert output_0['execution_count'] == output_1['execution_count'] or \ @@ -42,11 +51,36 @@ def _assert_cells_equal(cell_0, cell_1, check_metadata=True): assert cell_0['cell_type'] == cell_1['cell_type'] assert _cell_source(cell_0) == _cell_source(cell_1) for output_0, output_1 in zip(_cell_outputs(cell_0), _cell_outputs(cell_1)): - _assert_cell_outputs_equal(output_0, output_1, check_metadata=check_metadata) + _assert_cell_outputs_equal(output_0, output_1, + check_metadata=check_metadata) -def _assert_notebooks_equal(nb_0, nb_1, check_notebook_metadata=True, check_cell_metadata=True): +def _assert_notebooks_equal(nb_0, nb_1, check_notebook_metadata=True, + check_cell_metadata=True): if check_notebook_metadata: assert nb_0['metadata'] == nb_1['metadata'] for cell_0, cell_1 in zip(nb_0['cells'], nb_1['cells']): _assert_cells_equal(cell_0, cell_1, check_metadata=check_cell_metadata) + + +def _cell_input(cell): + """Return the input of an ipynb cell.""" + return _ensure_string(cell.get('source', [])) + + +def _cell_output(cell): + """Return the output of an ipynb cell.""" + outputs = cell.get('outputs', []) + # Add stdout. + stdout = ('\n'.join(_ensure_string(output.get('text', '')) + for output in outputs)).rstrip() + # Add text output. + text_outputs = [] + for output in outputs: + out = output.get('data', {}).get('text/plain', []) + out = _ensure_string(out) + # HACK: skip outputs. + if out.startswith(' Date: Fri, 3 Nov 2017 19:29:56 +0100 Subject: [PATCH 10/33] Implementing HtmlNbOutput. --- ipymd/formats/rmarkdown.py | 173 +++++++++++++++++++++----- ipymd/formats/tests/test_rmarkdown.py | 2 +- 2 files changed, 142 insertions(+), 33 deletions(-) diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 621113b..72c03be 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -13,6 +13,7 @@ import os.path import base64 +import json try: import nbformat as nbf @@ -23,12 +24,15 @@ from jinja2 import Environment, PackageLoader, select_autoescape -from ..utils.utils import _read_text, _write_text, _get_cell_types +from ..utils.utils import _read_text, _write_text, _get_cell_types, \ + _ensure_string from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, \ _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, \ _get_nb_html_path +from ..lib.notebook import _stream_output_to_result from .markdown import BaseMarkdownReader, BaseMarkdownWriter from ipymd.core.format_manager import convert +from ..ext.six import StringIO # ------------------------------------------------------------------------------ @@ -223,6 +227,9 @@ def __init__(self): def write_contents(self, nb): """convert a jupyter notebook dict to rmarkdown""" + # Parsing the notebook using nbf will get rid of the + # multi line strings. + # nb = nbf.from_dict(nb) assert nb['nbformat'] >= 4 self.write_notebook_metadata(nb['metadata']) @@ -234,6 +241,7 @@ def write(self, cell): self._nb_html_writer.write(cell) def write_notebook_metadata(self, metadata): + # metadata = json.loads(json.dumps(metadata)) self._rmd_writer.write_notebook_metadata(metadata) def close(self): @@ -250,37 +258,6 @@ def __del__(self): self.close() -class NbHtmlWriter(object): - """ Write R notebook .nb.html """ - def __init__(self, rmd_writer): - """ - - Parameters - ---------- - rmd_writer: RmdWriter - a reference to the corresponding RmdWriter, as the rmd output - will also be encoded in the html output. - """ - self._rmd_writer = rmd_writer - - @property - def template(self): - """ Load the jinja2 template from the package resources. """ - env = Environment( - loader=PackageLoader('ipymd', 'ressources'), - autoescape=select_autoescape(['html', 'xml']) - ) - return env.get_template('r_notebook.template.html') - - def write(self, cell): - pass - - @property - def contents(self): - base64_rmd = base64.b64encode(self._rmd_writer.contents.encode()) - return self.template.render(base64_rmd=base64_rmd) - - class RmdWriter(BaseMarkdownWriter): """Default .Rmd writer.""" @@ -288,6 +265,7 @@ def __init__(self): super(RmdWriter, self).__init__() def append_code(self, input, output=None, metadata=None): + input = _ensure_string(input) code_block = '```{{{meta}}}\n{code}\n```'.format( meta=self._encode_metadata(metadata), code=input.rstrip()) self._output.write(code_block) @@ -317,11 +295,142 @@ def encode_option(key, value): return "".join(out) + def write(self, cell): + """Write a ipynb cell to markdown""" + metadata = cell.get('metadata', None) + if cell['cell_type'] == 'markdown': + self.append_markdown(cell['source'], metadata) + elif cell['cell_type'] == 'code': + # output is not handled by RmdReader + self.append_code(cell['source'], output=None, metadata=metadata) + self._new_paragraph() + @property def contents(self): return self._output.getvalue().rstrip() + '\n' # end of file \n +class NbHtmlWriter(object): + """ Write R notebook .nb.html """ + def __init__(self, rmd_writer): + """ + + Parameters + ---------- + rmd_writer: RmdWriter + a reference to the corresponding RmdWriter, as the rmd output + will also be encoded in the html output. + """ + self._rmd_writer = rmd_writer + self._output = StringIO() + + @property + def template(self): + """ Load the jinja2 template from the package resources. """ + env = Environment( + loader=PackageLoader('ipymd', 'ressources'), + autoescape=select_autoescape(['html', 'xml']) + ) + return env.get_template('r_notebook.template.html') + + def append_markdown(self, markdown, metadata): + # TODO markdown to html, maybe check how it's done in Rmarkdown for full compatibility + self._output.write(self._create_tag('text', markdown, metadata)) + + def append_code(self, source, outputs, metadata): + child_tags = [] + child_tags.append( + self._create_tag('source', + tag_content=self._format_source(source, metadata), + tag_meta={'data': source}) + ) + for output in outputs: + child_tags.extend(self._create_output_tag(output)) + + self._output.write( + self._create_tag('chunk', "\n".join(child_tags)) + ) + + def _create_output_tag(self, output): + """yield tags such as + tag_meta: dict + meta-dictionary which can be added to the tag. + Will be base64 encoded. Example + tag_content: + html content which will be enclosed in the `begin` and `end` tags. + + Returns + ------- + str + \n + + """ + meta_b64 = "" if tag_meta is None else base64.b64encode( + json.dumps(tag_meta).encode('utf-8')) + return "\n" \ + "{contents}\n" \ + "\n".format(tag=tag_name, b64=meta_b64, + contents=tag_content) + + def write(self, cell): + metadata = cell.get('metadata', None) + if cell['cell_type'] == 'markdown': + self.append_markdown(cell['source'], metadata) + elif cell['cell_type'] == 'code': + self.append_code(cell['source'], cell['outputs'], metadata) + + @property + def contents(self): + base64_rmd = base64.b64encode(self._rmd_writer.contents.encode()) + return self.template.render(base64_rmd=base64_rmd) + + def load_rmarkdown(path): """ Read .Rmd and the corresponding .html.nb diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index b77cd71..a82770b 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -243,7 +243,7 @@ def _test_rmarkdown_writer(basename): in the correct .Rmd + .nb.html files""" contents = _read_test_file(basename, 'notebook') expected = _read_test_file(basename, 'rmarkdown') - converted = convert(contents, to_='rmarkdown', from_='notebook') + converted = convert(contents, to='rmarkdown', from_='notebook') assert converted['rmd'] == expected['rmd'] assert converted['html'] == expected['html'] From 87e40a283ca2ceb815447d0712c63a7e244c80d7 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 8 Nov 2017 14:51:08 +0100 Subject: [PATCH 11/33] update Rnotebook html template to latest version. --- ipymd/formats/rmarkdown.py | 16 ++++++++++- ipymd/ressources/r_notebook.template.html | 35 +++++------------------ 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 72c03be..3384ba1 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -270,6 +270,13 @@ def append_code(self, input, output=None, metadata=None): meta=self._encode_metadata(metadata), code=input.rstrip()) self._output.write(code_block) + def append_markdown(self, source, metadata): + source = _ensure_string(source) + if metadata is not None and len(metadata) > 0: + raise RuntimeWarning("Metadata for markdown cells is currently" + "not supported.") + self._output.write(source.rstrip()) + def _encode_metadata(self, metadata): def encode_option(key, value): return "{}={}".format(key, _option_value_str(value)) @@ -428,7 +435,14 @@ def write(self, cell): @property def contents(self): base64_rmd = base64.b64encode(self._rmd_writer.contents.encode()) - return self.template.render(base64_rmd=base64_rmd) + html_nb = self._output.getvalue().rstrip() + '\n' + # TODO is the filename in javascript necessary for anything_ + # the writer does not know anything about the filename + # therefore we use a hardcoded filename as workaround. + filename = "notebook.Rmd" + return self.template.render(filename=filename, + html_nb=html_nb, + base64_rmd=base64_rmd) def load_rmarkdown(path): diff --git a/ipymd/ressources/r_notebook.template.html b/ipymd/ressources/r_notebook.template.html index 135b586..53d4f8f 100644 --- a/ipymd/ressources/r_notebook.template.html +++ b/ipymd/ressources/r_notebook.template.html @@ -15,14 +15,14 @@ - + - + - + @@ -142,7 +142,7 @@ @@ -171,30 +171,9 @@

Example 5

- - +{{ html_nb }} -
{ base64_rmd }
+
{{ base64_rmd }}
@@ -235,4 +214,4 @@

Header

- \ No newline at end of file + From a295568108fb9c85b2e856415e166ef81479b309 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 8 Nov 2017 16:13:17 +0100 Subject: [PATCH 12/33] Update Example5 Added additional metadata to example 5. --- examples/ex5.notebook.ipynb | 4 +++- examples/ex5.rmarkdown.Rmd | 16 +++++++++++++++- examples/ex5.rmarkdown.nb.html | 14 +++++++------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/ex5.notebook.ipynb b/examples/ex5.notebook.ipynb index 0e4a72a..9def03b 100644 --- a/examples/ex5.notebook.ipynb +++ b/examples/ex5.notebook.ipynb @@ -77,7 +77,9 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.2" - } + }, + "output": "html_notebook", + "title": "Example 5" }, "nbformat": 4, "nbformat_minor": 2 diff --git a/examples/ex5.rmarkdown.Rmd b/examples/ex5.rmarkdown.Rmd index 9efb5de..2d0b0be 100644 --- a/examples/ex5.rmarkdown.Rmd +++ b/examples/ex5.rmarkdown.Rmd @@ -1,4 +1,18 @@ --- +kernelspec: + display_name: Python 3 + 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.6.2 output: html_notebook title: Example 5 --- @@ -9,7 +23,7 @@ A paragraph. test -```{python} +```{python, collapsed=TRUE} foo = 'bar' ``` diff --git a/examples/ex5.rmarkdown.nb.html b/examples/ex5.rmarkdown.nb.html index 632cc5f..f1f0058 100644 --- a/examples/ex5.rmarkdown.nb.html +++ b/examples/ex5.rmarkdown.nb.html @@ -15,14 +15,14 @@ - + - + - + @@ -142,7 +142,7 @@ @@ -178,8 +178,8 @@

Header

test

- -
foo = 'bar'
+ +
foo = 'bar' 
@@ -200,7 +200,7 @@

Header

-
LS0tCm91dHB1dDogaHRtbF9ub3RlYm9vawp0aXRsZTogRXhhbXBsZSA1Ci0tLQoKIyBIZWFkZXIKCkEgcGFyYWdyYXBoLgoKdGVzdAoKYGBge3B5dGhvbiB9CmZvbyA9ICdiYXInCmBgYAoKUHl0aG9uIGNvZGU6CgpgYGB7cHl0aG9uIH0KcHJpbnQoIkhlbGxvIHdvcmxkISIpCmBgYAoKSmF2YVNjcmlwdCBjb2RlOgoKYGBgamF2YXNjcmlwdApjb25zb2xlLmxvZygiSGVsbG8gd29ybGQhIik7CmBgYAoKdGVzdAo=
+
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDUKLS0tCgojIEhlYWRlcgoKQSBwYXJhZ3JhcGguCgp0ZXN0CgpgYGB7cHl0aG9uLCBjb2xsYXBzZWQ9VFJVRX0KZm9vID0gJ2JhcicKYGBgCgpQeXRob24gY29kZToKCmBgYHtweXRob259CnByaW50KCJIZWxsbyB3b3JsZCEiKQpgYGAKCkphdmFTY3JpcHQgY29kZToKCmBgYGphdmFzY3JpcHQKY29uc29sZS5sb2coIkhlbGxvIHdvcmxkISIpOwpgYGAKCnRlc3QK
From 4017c8e6bad61f17fa6c5fee81230fc3bf77bda1 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 8 Nov 2017 16:14:19 +0100 Subject: [PATCH 13/33] move markdown code formatting to own function. Like this it can be accessed as a helper function from a different place. --- ipymd/formats/markdown.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ipymd/formats/markdown.py b/ipymd/formats/markdown.py index 20828a3..40f0119 100644 --- a/ipymd/formats/markdown.py +++ b/ipymd/formats/markdown.py @@ -116,7 +116,8 @@ def __init__(self): def _new_paragraph(self): self._output.write('\n\n') - def meta(self, source, is_notebook=False): + @staticmethod + def meta(source, is_notebook=False): if source is None: return '' @@ -139,6 +140,11 @@ def meta(self, source, is_notebook=False): return meta + @staticmethod + def format_code(code, lang="python"): + return '```{lang}\n{code}\n```'.format(lang=lang, + code=code.rstrip()) + def append_markdown(self, source, metadata): source = _ensure_string(source) self._output.write(self.meta(metadata) + source.rstrip()) @@ -243,10 +249,11 @@ def __init__(self, prompt=None): def append_code(self, input, output=None, metadata=None): code = self._prompt.from_cell(input, output) - wrapped = '```python\n{code}\n```'.format(code=code.rstrip()) + wrapped = self.format_code(code) self._output.write(self.meta(metadata) + wrapped) + MARKDOWN_FORMAT = dict( reader=MarkdownReader, writer=MarkdownWriter, From 7b7eb980218ce752a13ff753effef9f06ef922be Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 8 Nov 2017 16:15:21 +0100 Subject: [PATCH 14/33] New features in NbHtmlWriter. html conversion requires pypandoc as dependency. --- ipymd/formats/rmarkdown.py | 58 +++++++++++++++++++++++--------------- ipymd/lib/rmarkdown.py | 6 ++++ setup.py | 2 +- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 3384ba1..c7efc63 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -12,8 +12,9 @@ import re import os.path -import base64 import json +import pypandoc +from html import escape as html_escape try: import nbformat as nbf @@ -22,13 +23,13 @@ import IPython.nbformat as nbf from IPython.nbformat.v4.nbbase import validate -from jinja2 import Environment, PackageLoader, select_autoescape +from jinja2 import Environment, PackageLoader, select_autoescape, Markup from ..utils.utils import _read_text, _write_text, _get_cell_types, \ _ensure_string from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, \ _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, \ - _get_nb_html_path + _get_nb_html_path, _b64_encode from ..lib.notebook import _stream_output_to_result from .markdown import BaseMarkdownReader, BaseMarkdownWriter from ipymd.core.format_manager import convert @@ -266,9 +267,12 @@ def __init__(self): def append_code(self, input, output=None, metadata=None): input = _ensure_string(input) - code_block = '```{{{meta}}}\n{code}\n```'.format( - meta=self._encode_metadata(metadata), code=input.rstrip()) - self._output.write(code_block) + self._output.write(self._code_block(input, metadata)) + + @staticmethod + def _code_block(code, meta): + return '```{{{meta}}}\n{code}\n```'.format( + meta=RmdWriter._encode_metadata(meta), code=code.rstrip()) def append_markdown(self, source, metadata): source = _ensure_string(source) @@ -277,7 +281,8 @@ def append_markdown(self, source, metadata): "not supported.") self._output.write(source.rstrip()) - def _encode_metadata(self, metadata): + @staticmethod + def _encode_metadata(metadata): def encode_option(key, value): return "{}={}".format(key, _option_value_str(value)) @@ -341,21 +346,29 @@ def template(self): return env.get_template('r_notebook.template.html') def append_markdown(self, markdown, metadata): - # TODO markdown to html, maybe check how it's done in Rmarkdown for full compatibility - self._output.write(self._create_tag('text', markdown, metadata)) + markdown = _ensure_string(markdown) + html = pypandoc.convert_text(markdown, 'html', format='md') + self._output.write(self._create_tag('text', html, metadata) + "\n") def append_code(self, source, outputs, metadata): + source = _ensure_string(source) + + # Markdown representation of code is given as b64 in nb.html + # TODO derive default lang from kernel + lang = metadata.get('lang', 'python') + source_as_markdown = BaseMarkdownWriter.format_code(source, lang) + child_tags = [] child_tags.append( self._create_tag('source', tag_content=self._format_source(source, metadata), - tag_meta={'data': source}) + tag_meta={'data': source_as_markdown}) ) for output in outputs: child_tags.extend(self._create_output_tag(output)) self._output.write( - self._create_tag('chunk', "\n".join(child_tags)) + self._create_tag('chunk', "\n".join(child_tags) + "\n") + "\n" ) def _create_output_tag(self, output): @@ -366,8 +379,8 @@ def _create_output_tag(self, output): try: text = output['data'].pop('text/plain') yield self._create_tag('output', - self._format_text_output(text), - output['metadata']) + tag_content=self._format_text_output(text), + tag_meta={'data': text}) except KeyError: pass @@ -375,8 +388,9 @@ def _create_output_tag(self, output): for mime, data in output['data'].items(): if mime.startswith('image/'): yield self._create_tag('plot', - self._format_image(mime, data), - output['metadata']) + tag_content=self._format_image( + mime, data), + tag_meta=output['metadata']) else: raise RuntimeError("Invalid output mime-type: {}".format(mime)) @@ -385,8 +399,8 @@ def _format_source(source, metadata): """Format contents of source tag. """ # TODO default lang? lang = metadata.get('lang', 'raw') - return '
{source}/code>
'.format( - lang=lang, source=source + return '
{source}
'.format( + lang=lang, source=html_escape(source) ) @staticmethod @@ -418,11 +432,11 @@ def _create_tag(tag_name, tag_content, tag_meta=None): \n """ - meta_b64 = "" if tag_meta is None else base64.b64encode( - json.dumps(tag_meta).encode('utf-8')) + meta_b64 = "" if tag_meta is None or tag_meta == {} else _b64_encode( + json.dumps(tag_meta)) return "\n" \ "{contents}\n" \ - "\n".format(tag=tag_name, b64=meta_b64, + "".format(tag=tag_name, b64=meta_b64, contents=tag_content) def write(self, cell): @@ -434,8 +448,8 @@ def write(self, cell): @property def contents(self): - base64_rmd = base64.b64encode(self._rmd_writer.contents.encode()) - html_nb = self._output.getvalue().rstrip() + '\n' + base64_rmd = _b64_encode(self._rmd_writer.contents) + html_nb = Markup(self._output.getvalue().rstrip() + '\n') # TODO is the filename in javascript necessary for anything_ # the writer does not know anything about the filename # therefore we use a hardcoded filename as workaround. diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py index a35dd0b..f4ce5b4 100644 --- a/ipymd/lib/rmarkdown.py +++ b/ipymd/lib/rmarkdown.py @@ -31,6 +31,12 @@ } +def _b64_encode(text): + """Encode a string to base64. Unlike base64.b64encode, + input and output are utf-8 strings. """ + return base64.b64encode(text.encode('utf-8')).decode('utf-8') + + def _tokenize_chunk_options(options_line): """ Break an options line into a list of tokens. diff --git a/setup.py b/setup.py index 33c5c88..e79490b 100644 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ def run_tests(self): 'python=ipymd.formats.python:PYTHON_FORMAT', ] }, - install_requires=['pyyaml', 'jinja2'], + install_requires=['pyyaml', 'jinja2', 'pypandoc'], extras_require={ 'odf': ['odfpy'], }, From 9c7bb46b5d72bc0928bdc61522186a2361100183 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 8 Nov 2017 17:38:34 +0100 Subject: [PATCH 15/33] update example6 --- examples/ex6.notebook.ipynb | 39 ++++++++++++--------------- examples/ex6.rmarkdown.Rmd | 22 ++++++++++++--- examples/ex6.rmarkdown.nb.html | 49 +++++++++++++++++++++------------- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/examples/ex6.notebook.ipynb b/examples/ex6.notebook.ipynb index 9e85819..b5a4404 100644 --- a/examples/ex6.notebook.ipynb +++ b/examples/ex6.notebook.ipynb @@ -18,28 +18,29 @@ "name": "stdout", "output_type": "stream", "text": [ - "ä 0\n", - "ä 1\n", - "ä 2\n", - "ä 3\n", - "ä 4\n", - "ä 5\n", - "ä 6\n", - "ä 7\n", - "ä 8\n", - "ä 9\n" + "ä'<>$& 0\n", + "ä'<>$& 1\n", + "ä'<>$& 2\n", + "ä'<>$& 3\n", + "ä'<>$& 4\n", + "ä'<>$& 5\n", + "ä'<>$& 6\n", + "ä'<>$& 7\n", + "ä'<>$& 8\n", + "ä'<>$& 9\n" ] } ], "source": [ "for i in range(10):\n", - " print(\"ä \" + str(i))" + " print(\"ä'<>$& \" + str(i))" ] }, { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + }, "outputs": [], "source": [ "import seaborn as sns\n", @@ -54,7 +55,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 3, @@ -65,7 +66,7 @@ "data": { "image/png": "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\n0MS/AXc8VdgXcacAAAAAAElFTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -80,14 +81,8 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, + "metadata": { + }, "outputs": [], "source": [] } diff --git a/examples/ex6.rmarkdown.Rmd b/examples/ex6.rmarkdown.Rmd index b6c1fec..76cb9f9 100644 --- a/examples/ex6.rmarkdown.Rmd +++ b/examples/ex6.rmarkdown.Rmd @@ -1,23 +1,37 @@ +--- +kernelspec: + display_name: Python 3 + 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.6.2 +--- + Foo bar kk $\sum_{i=1}^n 2^i$ ```{python} for i in range(10): - print("ä " + str(i)) + print("ä'<>$& " + str(i)) ``` ```{python} import seaborn as sns %matplotlib inline -from pylab import * ``` ```{python} import seaborn as sns sns.boxplot(range(10)) -figure() -plot(range(10)) ``` ```{python} diff --git a/examples/ex6.rmarkdown.nb.html b/examples/ex6.rmarkdown.nb.html index 135b586..3f30d9d 100644 --- a/examples/ex6.rmarkdown.nb.html +++ b/examples/ex6.rmarkdown.nb.html @@ -11,18 +11,18 @@ -Example 5 +Example 6 - + - + - + @@ -142,7 +142,7 @@ @@ -166,35 +166,48 @@ -

Example 5

+

Example 6

- + + +
import seaborn as sns
+sns.boxplot(range(10))
+figure()
+plot(range(10))
+ + + + + + -
{ base64_rmd }
+
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDYKLS0tCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCiVtYXRwbG90bGliIGlubGluZQpmcm9tIHB5bGFiIGltcG9ydCAqCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCnNucy5ib3hwbG90KHJhbmdlKDEwKSkKZmlndXJlKCkKcGxvdChyYW5nZSgxMCkpCmBgYAoKYGBge3B5dGhvbn0KCmBgYAo=
@@ -235,4 +248,4 @@

Header

- \ No newline at end of file + From a42a16e900339fe04110b64f83900add591b2f6b Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 8 Nov 2017 17:40:35 +0100 Subject: [PATCH 16/33] Bug fixes in NbHtmlReader --- ipymd/formats/rmarkdown.py | 9 +++++---- ipymd/formats/tests/test_rmarkdown.py | 10 +++++++--- ipymd/lib/rmarkdown.py | 3 +-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index c7efc63..5f1fdf3 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -198,7 +198,8 @@ def _chunk_cell(self, chunk_block): # # "source", "plot", "output", # "warning", "error", "message" - cell = None + cell = HtmlNbChunkCell(self._count) + self._count += 1 for start_tag, b64, contents, end_tag in self.def_chunk_element.findall(chunk_block): assert start_tag == end_tag, \ @@ -206,7 +207,8 @@ def _chunk_cell(self, chunk_block): b64 = b64.strip() contents = contents.strip() if start_tag == 'source': - cell = HtmlNbChunkCell(b64, self._count) + # we ignore the source as we obtain it from .Rmd + pass elif start_tag in ['output', 'warning', 'error', 'message']: assert cell is not None, "output without source" cell.new_output(start_tag, b64) @@ -215,7 +217,6 @@ def _chunk_cell(self, chunk_block): mime, data = self._parse_image(contents) cell.new_plot(mime, data, b64) - self._count += 1 return cell.cell @@ -374,7 +375,7 @@ def append_code(self, source, outputs, metadata): def _create_output_tag(self, output): """yield tags such as \n' + '

\n' + '') + mime, data = list(htmlnbreader._parse_image(html)) + assert mime == 'image/png' + assert data == "iVBORw0KGgoAAAANSUhEUgAAAWQAAAD8CAYAAABAWd66AAAABHNCSV" \ + "QICAgIfAhkiAAAAAlwSFlz\n" \ + "AAALEgAACxIB0t1+/AAACDBJREFUeJzt3c2LnWcZx/Hf1RmlScW" \ + "XklJwWpzKiCUIUglSLbiwLnxD\n" html = """

some other html

""" mime, data = list(htmlnbreader._parse_image(html)) @@ -263,6 +276,19 @@ def _test_rmarkdown_rmarkdown(basename): assert converted['html'] == contents['html'] +def _test_notebook_notebook(basename): + """check that converting a notebook to Rmarkdown and back + is the identity""" + # makes only sense with verbose metadata + _fm = format_manager() + _fm.verbose_metadata = True + contents = _read_test_file(basename, 'notebook') + rmarkdown = convert(contents, from_='notebook', to='rmarkdown') + converted = convert(rmarkdown, from_='rmarkdown', to='notebook') + + _assert_notebooks_equal(contents, converted, check_cell_metadata=False) + + def test_ex5_reader(): _test_rmarkdown_reader('ex5') @@ -285,3 +311,7 @@ def test_ex6_writer(): def test_ex6_rmarkdown_rmarkdown(): _test_rmarkdown_rmarkdown('ex6') + + +def test_ex6_notebook_notebook(): + _test_notebook_notebook('ex6') diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py index b8d9636..811a55a 100644 --- a/ipymd/lib/rmarkdown.py +++ b/ipymd/lib/rmarkdown.py @@ -220,11 +220,12 @@ def new_output(self, tag, b64): metadata={"output_type": tag})) def new_plot(self, mime, data, b64): + meta = {} if not b64 else _read_rmd_b64(b64) self._cell.outputs.append( nbf.v4.new_output('execute_result', {mime: data}, execution_count=self._count, - metadata=_read_rmd_b64(b64)) + metadata=meta) ) @property From 60112fdd3d95d784c5e6708747a0858afa707ea7 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 9 Nov 2017 17:24:36 +0100 Subject: [PATCH 18/33] derive cell language from jupyter kernel. --- ipymd/formats/rmarkdown.py | 82 ++++++++++++++------------- ipymd/formats/tests/test_rmarkdown.py | 4 +- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 9df555f..2029e6e 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -75,7 +75,8 @@ def _merge_notebooks(self, nb_rmd, nb_html): rmd_cell['outputs'] = html_cell['outputs'] return nb_rmd - def _check_notebook_consistency(self, rmd_cells, html_cells): + @staticmethod + def _check_notebook_consistency(rmd_cells, html_cells): """The lists of cells are considered consistent if the cell types appear in the same order. """ return _get_cell_types(rmd_cells) == _get_cell_types(html_cells) @@ -187,7 +188,8 @@ def _parse_image(self, html): data = 'IPYMD: Error reading image.' return mime, data - def _text_cell(self, text_block): + @staticmethod + def _text_cell(text_block): # new markdown cell will be filled with html. The corresponding # source is found in .Rmd file. return nbf.v4.new_markdown_cell(text_block) @@ -223,9 +225,12 @@ def _chunk_cell(self, chunk_block): class RmarkdownWriter(object): """Write R notebook (combine .Rmd and .nb.html) """ + DEFAULT_LANGUAGE = "python" # if, for whatever reason, no metadata is set + def __init__(self): - self._rmd_writer = RmdWriter() - self._nb_html_writer = NbHtmlWriter(self._rmd_writer) + self._language = self.DEFAULT_LANGUAGE + self.rmd_writer = RmdWriter(self) + self.nb_html_writer = NbHtmlWriter(self) def write_contents(self, nb): """convert a jupyter notebook dict to rmarkdown""" @@ -234,26 +239,35 @@ def write_contents(self, nb): # nb = nbf.from_dict(nb) assert nb['nbformat'] >= 4 + try: + self._language = nb['metadata']['kernelspec']['language'] + except KeyError: + pass + self.write_notebook_metadata(nb['metadata']) for cell in nb['cells']: self.write(cell) def write(self, cell): - self._rmd_writer.write(cell) - self._nb_html_writer.write(cell) + self.rmd_writer.write(cell) + self.nb_html_writer.write(cell) def write_notebook_metadata(self, metadata): - # metadata = json.loads(json.dumps(metadata)) - self._rmd_writer.write_notebook_metadata(metadata) + self.rmd_writer.write_notebook_metadata(metadata) def close(self): - self._rmd_writer.close() + self.rmd_writer.close() + + @property + def kernel_lang(self): + """get the kernel language""" + return self._language @property def contents(self): return { - 'rmd': self._rmd_writer.contents, - 'html': self._nb_html_writer.contents + 'rmd': self.rmd_writer.contents, + 'html': self.nb_html_writer.contents } def __del__(self): @@ -263,17 +277,17 @@ def __del__(self): class RmdWriter(BaseMarkdownWriter): """Default .Rmd writer.""" - def __init__(self): + def __init__(self, rmarkdown_writer): super(RmdWriter, self).__init__() + self._rmarkdown_writer = rmarkdown_writer def append_code(self, input, output=None, metadata=None): input = _ensure_string(input) self._output.write(self._code_block(input, metadata)) - @staticmethod - def _code_block(code, meta): + def _code_block(self, code, meta): return '```{{{meta}}}\n{code}\n```'.format( - meta=RmdWriter._encode_metadata(meta), code=code.rstrip()) + meta=self._encode_metadata(meta), code=code.rstrip()) def append_markdown(self, source, metadata): source = _ensure_string(source) @@ -282,23 +296,16 @@ def append_markdown(self, source, metadata): "not supported.") self._output.write(source.rstrip()) - @staticmethod - def _encode_metadata(metadata): + def _encode_metadata(self, metadata): def encode_option(key, value): return "{}={}".format(key, _option_value_str(value)) - if metadata is not None: - # TODO derive default lang from kernel - lang = metadata.pop('lang', 'python') - name = metadata.pop('name', None) - options = ", ".join(encode_option(k, v) - for k, v in metadata.items()) + metadata = {} if metadata is None else metadata - else: - # TODO tmp workaround - lang = "python" - name = None - options = "" + lang = metadata.pop('lang', self._rmarkdown_writer.kernel_lang) + name = metadata.pop('name', None) + options = ", ".join(encode_option(k, v) + for k, v in metadata.items()) out = [lang] if name is not None: @@ -325,16 +332,16 @@ def contents(self): class NbHtmlWriter(object): """ Write R notebook .nb.html """ - def __init__(self, rmd_writer): + def __init__(self, rmarkdown_writer): """ Parameters ---------- - rmd_writer: RmdWriter - a reference to the corresponding RmdWriter, as the rmd output - will also be encoded in the html output. + rmarkdown_writer: RmarkdownWriter + a reference to the parent RmarkdownWriter """ - self._rmd_writer = rmd_writer + self._rmarkdown_writer = rmarkdown_writer + self._rmd_writer = rmarkdown_writer.rmd_writer self._output = StringIO() @property @@ -355,14 +362,13 @@ def append_code(self, source, outputs, metadata): source = _ensure_string(source) # Markdown representation of code is given as b64 in nb.html - # TODO derive default lang from kernel - lang = metadata.get('lang', 'python') + lang = metadata.get('lang', self._rmarkdown_writer.kernel_lang) source_as_markdown = BaseMarkdownWriter.format_code(source, lang) child_tags = [] child_tags.append( self._create_tag('source', - tag_content=self._format_source(source, metadata), + tag_content=self._format_source(source, lang), tag_meta={'data': source_as_markdown}) ) for output in outputs: @@ -396,10 +402,8 @@ def _create_output_tag(self, output): raise RuntimeError("Invalid output mime-type: {}".format(mime)) @staticmethod - def _format_source(source, metadata): + def _format_source(source, lang): """Format contents of source tag. """ - # TODO default lang? - lang = metadata.get('lang', 'raw') return '
{source}
'.format( lang=lang, source=html_escape(source) ) diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index 4e11bd8..83bf24e 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -61,7 +61,7 @@ def test_htmlnb_parse_image(): html = """

some other html

""" mime, data = list(htmlnbreader._parse_image(html)) assert mime == 'text/plain' - assert data == 'Error reading image.' + assert data == 'IPYMD: Error reading image.' def test_htmlnb_chunk_cell(): @@ -229,7 +229,7 @@ def test_rmd_write_cell_metadata(): ('bool_type', True), ('null_type', None)]) - rmdreader = RmdWriter() + rmdreader = RmdWriter(RmarkdownWriter()) result = rmdreader._encode_metadata(chunk_meta) # we know that all strings will be double-quoted. expected = expected.replace("'", '"') From 70e2ba4148009ba989f35e55424e1506a0ca2cd6 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 15 Nov 2017 11:39:22 +0100 Subject: [PATCH 19/33] Differ between rstudio Rmd and ipymd Rmd for better testing. There is no way we can reproduce the *exact* .nb.html as produced by rstudio. The difference is, however, irrelveant (e.g. different ways of rendering markdown to html). We now have two test-files: ipymd should be able to *read* rstudio Rmd, and to *read and write* ipymd Rmd. --- examples/ex5.rmarkdown.rstudio.Rmd | 42 +++++ examples/ex5.rmarkdown.rstudio.nb.html | 244 ++++++++++++++++++++++++ examples/ex6.rmarkdown.rstudio.Rmd | 39 ++++ examples/ex6.rmarkdown.rstudio.nb.html | 251 +++++++++++++++++++++++++ ipymd/formats/tests/test_rmarkdown.py | 87 +++++++-- ipymd/lib/rmarkdown.py | 3 +- ipymd/utils/utils.py | 7 + 7 files changed, 659 insertions(+), 14 deletions(-) create mode 100644 examples/ex5.rmarkdown.rstudio.Rmd create mode 100644 examples/ex5.rmarkdown.rstudio.nb.html create mode 100644 examples/ex6.rmarkdown.rstudio.Rmd create mode 100644 examples/ex6.rmarkdown.rstudio.nb.html diff --git a/examples/ex5.rmarkdown.rstudio.Rmd b/examples/ex5.rmarkdown.rstudio.Rmd new file mode 100644 index 0000000..2d0b0be --- /dev/null +++ b/examples/ex5.rmarkdown.rstudio.Rmd @@ -0,0 +1,42 @@ +--- +kernelspec: + display_name: Python 3 + 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.6.2 +output: html_notebook +title: Example 5 +--- + +# Header + +A paragraph. + +test + +```{python, collapsed=TRUE} +foo = 'bar' +``` + +Python code: + +```{python} +print("Hello world!") +``` + +JavaScript code: + +```javascript +console.log("Hello world!"); +``` + +test diff --git a/examples/ex5.rmarkdown.rstudio.nb.html b/examples/ex5.rmarkdown.rstudio.nb.html new file mode 100644 index 0000000..f1f0058 --- /dev/null +++ b/examples/ex5.rmarkdown.rstudio.nb.html @@ -0,0 +1,244 @@ + + + + + + + + + + + + + +Example 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDUKLS0tCgojIEhlYWRlcgoKQSBwYXJhZ3JhcGguCgp0ZXN0CgpgYGB7cHl0aG9uLCBjb2xsYXBzZWQ9VFJVRX0KZm9vID0gJ2JhcicKYGBgCgpQeXRob24gY29kZToKCmBgYHtweXRob259CnByaW50KCJIZWxsbyB3b3JsZCEiKQpgYGAKCkphdmFTY3JpcHQgY29kZToKCmBgYGphdmFzY3JpcHQKY29uc29sZS5sb2coIkhlbGxvIHdvcmxkISIpOwpgYGAKCnRlc3QK
+ + + +
+ + + + + + + + diff --git a/examples/ex6.rmarkdown.rstudio.Rmd b/examples/ex6.rmarkdown.rstudio.Rmd new file mode 100644 index 0000000..76cb9f9 --- /dev/null +++ b/examples/ex6.rmarkdown.rstudio.Rmd @@ -0,0 +1,39 @@ +--- +kernelspec: + display_name: Python 3 + 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.6.2 +--- + +Foo bar kk + +$\sum_{i=1}^n 2^i$ + +```{python} +for i in range(10): + print("ä'<>$& " + str(i)) +``` + +```{python} +import seaborn as sns +%matplotlib inline +``` + +```{python} +import seaborn as sns +sns.boxplot(range(10)) +``` + +```{python} + +``` diff --git a/examples/ex6.rmarkdown.rstudio.nb.html b/examples/ex6.rmarkdown.rstudio.nb.html new file mode 100644 index 0000000..3f30d9d --- /dev/null +++ b/examples/ex6.rmarkdown.rstudio.nb.html @@ -0,0 +1,251 @@ + + + + + + + + + + + + + +Example 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +

Foo bar kk

+

\(\sum_{i=1}^n 2^i\)

+ + + +
plot(1:10)
+ + +

+ + + + + + +
import seaborn as sns
+%matplotlib inline
+from pylab import *
+ + + + + + +
import seaborn as sns
+sns.boxplot(range(10))
+figure()
+plot(range(10))
+ + + + + + + +
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDYKLS0tCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCiVtYXRwbG90bGliIGlubGluZQpmcm9tIHB5bGFiIGltcG9ydCAqCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCnNucy5ib3hwbG90KHJhbmdlKDEwKSkKZmlndXJlKCkKcGxvdChyYW5nZSgxMCkpCmBgYAoKYGBge3B5dGhvbn0KCmBgYAo=
+ + + +
+ + + + + + + + diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index 83bf24e..bfb3051 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -8,9 +8,11 @@ from ipymd.core.format_manager import convert, format_manager from ._utils import _read_test_file +from ...utils.utils import _full_diff from ...lib.notebook import _assert_notebooks_equal from ipymd.formats.rmarkdown import HtmlNbChunkCell, RmarkdownWriter, \ - RmarkdownReader, RmdWriter, RmdReader, NbHtmlWriter, HtmlNbReader + RmarkdownReader, RmdWriter, RmdReader, NbHtmlWriter, HtmlNbReader, \ + RMD_FORMAT from collections import OrderedDict import json @@ -112,7 +114,7 @@ def test_htmlnb_chunk_cell2(): # list of output dicts (described below) "execution_count": 1, "output_type": "execute_result", - "metadata": {'output_type': 'output'}, + "metadata": {}, "data": {'text/plain': "[1] 1 2 3 4 5 6 7 8 9 10"} }] } @@ -188,7 +190,7 @@ def test_rmarkdown_merge_cells(): assert cell['source'] == 'print(1:10)' assert cell['outputs'] == [{ "output_type": "execute_result", - 'metadata': {'output_type': 'output'}, + 'metadata': {}, 'data': {"text/plain": "[1] 1 2 3 4 5 6 7 8 9 10"}, "execution_count": 1 }] @@ -240,15 +242,63 @@ def test_rmd_write_cell_metadata(): # Test Format # ------------------------------------------------------------------------------ +""" +Testing reading and writing of Rmd notebooks. + +We have the issue that we cannot *exactly* reproduce the HTML +generated by rstudio (and there is no point in trying hard +to do so, as this might also change over time). + +Therefore, an assertion for *writing* exactly the same .nb.html is futile. +We circumvent this problem by splitting up the test files in +*.rstudio.{rmd,nb.html} and *.{rmd,nb.html}. + +While the reader should be able to correctly read both of the file formats +(i.e. nb.html as generated by rstudio and ipymd), the writer only has +to be consistent with the ipymd version. + +## The round-trip-conversion test +By checking that rmarkdown can be correctly converted to rmarkdown +we ensure that no information is lost. + +By checking that a notebook can be correctly converted to a notebook +and back, we ensure that a notebook can be represented +in rmarkdown without any information loss, i.e. rmarkdown is a +full replacement for .ipynb. + +## Compatibility with rstudio +While we can ensure automatically, that reading rmarkdown generated +with rstudio works, we cannot test (easily) that rstudio correctly +reads rmarkdown generated by ipymd. This would require to implement +tests in R instead of python. +""" + +# register rstudio rmarkdown dialect to the format manager. +_fm = format_manager() +_fm.register(name='rmarkdown.rstudio', **RMD_FORMAT) +# ipymd only makes sense with verbose metadata +_fm.verbose_metadata = True + def _test_rmarkdown_reader(basename): """Check that reading Rmarkdown (.Rmd + .nb.html) results - in the correct notebook (.ipynb). """ + in the correct notebook (.ipynb). + """ contents = _read_test_file(basename, 'rmarkdown') + contents_rstudio = _read_test_file(basename, 'rmarkdown.rstudio') expected = _read_test_file(basename, 'notebook') converted = convert(contents, from_='rmarkdown', to='notebook') - # TODO check metadata - _assert_notebooks_equal(expected, converted, check_cell_metadata=False, + converted_rstudio = convert(contents_rstudio, from_='rmarkdown', + to='notebook') + + # for ipymd rmd notebook, metadata must be equal + _assert_notebooks_equal(expected, converted, check_cell_metadata=True, + check_notebook_metadata=True) + + # we must be able to correctly read an Rmd created by rstudio, + # but we cannot expect all metadata to be there + _assert_notebooks_equal(expected, converted_rstudio, + check_cell_metadata=False, check_notebook_metadata=False) @@ -257,16 +307,29 @@ def _test_rmarkdown_writer(basename): in the correct .Rmd + .nb.html files""" contents = _read_test_file(basename, 'notebook') expected = _read_test_file(basename, 'rmarkdown') + expected_rstudio = _read_test_file(basename, 'rmarkdown.rstudio') converted = convert(contents, to='rmarkdown', from_='notebook') + + # The writer must produce exactly the rmd/html we want assert converted['rmd'] == expected['rmd'] assert converted['html'] == expected['html'] + # However, we cannot expect to generate exactly the same result + # as rstudio (minor things like markdown->HTML conversion. + # We want to have a diff for development purposes, though + print("Diff converted Rmd with {}.rmarkdown.rstudio.Rmd\n".format( + basename)) + print(_full_diff(converted['rmd'], expected_rstudio['rmd'])) + print('\n\n') + + print("Diff converted nb.html with {}.rmarkdown.rstudio.nb.html\n".format( + basename)) + print(_full_diff(converted['html'], expected_rstudio['html'])) + print('\n\n') + def _test_rmarkdown_rmarkdown(basename): """Check that the double conversion is the identity.""" - # makes only sense with verbose metadata - _fm = format_manager() - _fm.verbose_metadata = True contents = _read_test_file(basename, 'rmarkdown') notebook = convert(contents, from_='rmarkdown', to='notebook') notebook_dict = json.loads(json.dumps(notebook)) @@ -279,14 +342,12 @@ def _test_rmarkdown_rmarkdown(basename): def _test_notebook_notebook(basename): """check that converting a notebook to Rmarkdown and back is the identity""" - # makes only sense with verbose metadata - _fm = format_manager() - _fm.verbose_metadata = True contents = _read_test_file(basename, 'notebook') rmarkdown = convert(contents, from_='notebook', to='rmarkdown') converted = convert(rmarkdown, from_='rmarkdown', to='notebook') - _assert_notebooks_equal(contents, converted, check_cell_metadata=False) + # TODO cell metadata? unimportant metadata(collapsed...) + _assert_notebooks_equal(contents, converted, check_cell_metadata=True) def test_ex5_reader(): diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py index 811a55a..f12bbf5 100644 --- a/ipymd/lib/rmarkdown.py +++ b/ipymd/lib/rmarkdown.py @@ -217,7 +217,8 @@ def new_output(self, tag, b64): {'text/plain': _read_rmd_b64(b64)['data'].strip()}, execution_count=self._count, - metadata={"output_type": tag})) + # metadata={"output_type": tag})) + metadata={})) def new_plot(self, mime, data, b64): meta = {} if not b64 else _read_rmd_b64(b64) diff --git a/ipymd/utils/utils.py b/ipymd/utils/utils.py index 83b3188..f4b6f57 100644 --- a/ipymd/utils/utils.py +++ b/ipymd/utils/utils.py @@ -98,6 +98,13 @@ def _diff(text_0, text_1): return _diff_removed_lines(diff) +def _full_diff(text_0, text_1): + """Return a full diff between two strings""" + diff = difflib.ndiff(text_0.splitlines(keepends=True), + text_1.splitlines(keepends=True)) + return "".join(diff) + + def _show_outputs(*outputs): for output in outputs: print() From 31b978dfac84c68267081793802be94ba72f94ff Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Wed, 15 Nov 2017 17:15:47 +0100 Subject: [PATCH 20/33] Fix bugs related to ex5 unittest. Implement 'error' output type. --- examples/ex5.rmarkdown.nb.html | 15 ++--- ipymd/formats/rmarkdown.py | 62 ++++++++++++++--- ipymd/formats/tests/test_rmarkdown.py | 81 ++++++++++++++++++----- ipymd/lib/rmarkdown.py | 18 +++++ ipymd/ressources/r_notebook.template.html | 4 +- 5 files changed, 145 insertions(+), 35 deletions(-) diff --git a/examples/ex5.rmarkdown.nb.html b/examples/ex5.rmarkdown.nb.html index f1f0058..acd1e9c 100644 --- a/examples/ex5.rmarkdown.nb.html +++ b/examples/ex5.rmarkdown.nb.html @@ -172,33 +172,32 @@

Example 5

- +
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDUKLS0tCgojIEhlYWRlcgoKQSBwYXJhZ3JhcGguCgp0ZXN0CgpgYGB7cHl0aG9uLCBjb2xsYXBzZWQ9VFJVRX0KZm9vID0gJ2JhcicKYGBgCgpQeXRob24gY29kZToKCmBgYHtweXRob259CnByaW50KCJIZWxsbyB3b3JsZCEiKQpgYGAKCkphdmFTY3JpcHQgY29kZToKCmBgYGphdmFzY3JpcHQKY29uc29sZS5sb2coIkhlbGxvIHdvcmxkISIpOwpgYGAKCnRlc3QK
diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 2029e6e..d830483 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -14,7 +14,6 @@ import json import pypandoc -from html import escape as html_escape try: import nbformat as nbf @@ -29,7 +28,7 @@ _ensure_string from ..lib.rmarkdown import _option_value_str, _parse_chunk_meta, \ _merge_consecutive_markdown_cells, _is_code_chunk, HtmlNbChunkCell, \ - _get_nb_html_path, _b64_encode + _get_nb_html_path, _b64_encode, html_escape from ..lib.notebook import _stream_output_to_result from .markdown import BaseMarkdownReader, BaseMarkdownWriter from ipymd.core.format_manager import convert @@ -211,9 +210,12 @@ def _chunk_cell(self, chunk_block): if start_tag == 'source': # we ignore the source as we obtain it from .Rmd pass - elif start_tag in ['output', 'warning', 'error', 'message']: + elif start_tag in ['output', 'warning', 'message']: assert cell is not None, "output without source" cell.new_output(start_tag, b64) + elif start_tag == 'error': + assert cell is not None, "error without source" + cell.new_error(b64) elif start_tag == 'plot': assert cell is not None, "plot without source" mime, data = self._parse_image(contents) @@ -254,6 +256,7 @@ def write(self, cell): def write_notebook_metadata(self, metadata): self.rmd_writer.write_notebook_metadata(metadata) + self.nb_html_writer.write_notebook_metadata(metadata) def close(self): self.rmd_writer.close() @@ -343,6 +346,7 @@ def __init__(self, rmarkdown_writer): self._rmarkdown_writer = rmarkdown_writer self._rmd_writer = rmarkdown_writer.rmd_writer self._output = StringIO() + self._metadata = {} @property def template(self): @@ -381,8 +385,33 @@ def append_code(self, source, outputs, metadata): def _create_output_tag(self, output): """yield tags such as \n" \ - "{contents}\n" \ + "{contents}" \ "".format(tag=tag_name, b64=meta_b64, contents=tag_content) @@ -451,15 +487,21 @@ def write(self, cell): elif cell['cell_type'] == 'code': self.append_code(cell['source'], cell['outputs'], metadata) + def write_notebook_metadata(self, metadata): + self._metadata = metadata + @property def contents(self): base64_rmd = _b64_encode(self._rmd_writer.contents) html_nb = Markup(self._output.getvalue().rstrip() + '\n') # TODO is the filename in javascript necessary for anything_ + # TODO set title (-> filename) # the writer does not know anything about the filename # therefore we use a hardcoded filename as workaround. - filename = "notebook.Rmd" + filename = self._metadata.get("filename", "notebook.Rmd") + title = self._metadata.get("title", "IPYMD Notebook") return self.template.render(filename=filename, + title=title, html_nb=html_nb, base64_rmd=base64_rmd) diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index bfb3051..0b8790a 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -66,6 +66,28 @@ def test_htmlnb_parse_image(): assert data == 'IPYMD: Error reading image.' +def test_htmlnb_parse_error(): + error_output = { + "ename": "SyntaxError", + "evalue": "invalid syntax (, line 3)", + "output_type": "error", + "traceback": ["\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b" + "[0m\n\u001b[0;31m knitr::knit_engines$set(python =" + " reticulate::eng_python)\u001b[0m\n\u001b[0m " + "^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:" + "\u001b[0m invalid syntax\n"] + } + rmarkdown_writer = RmarkdownWriter() + nb_html_writer = NbHtmlWriter(rmarkdown_writer) + nb_html_reader = HtmlNbReader() + html_chunk = "".join(nb_html_writer._create_output_tag(error_output)) + + cell = nb_html_reader._chunk_cell(html_chunk) + + assert cell.outputs[0] == error_output + + def test_htmlnb_chunk_cell(): html = """ @@ -285,16 +307,23 @@ def _test_rmarkdown_reader(basename): in the correct notebook (.ipynb). """ contents = _read_test_file(basename, 'rmarkdown') - contents_rstudio = _read_test_file(basename, 'rmarkdown.rstudio') expected = _read_test_file(basename, 'notebook') converted = convert(contents, from_='rmarkdown', to='notebook') - converted_rstudio = convert(contents_rstudio, from_='rmarkdown', - to='notebook') # for ipymd rmd notebook, metadata must be equal _assert_notebooks_equal(expected, converted, check_cell_metadata=True, check_notebook_metadata=True) + +def _test_rmarkdown_reader_rstudio(basename): + """Check that reading Rmarkdown (.Rmd + .nb.html) generated + by rstudio results in the correct notebook (.ipynb) + ignoring metadata""" + contents_rstudio = _read_test_file(basename, 'rmarkdown.rstudio') + expected = _read_test_file(basename, 'notebook') + converted_rstudio = convert(contents_rstudio, from_='rmarkdown', + to='notebook') + # we must be able to correctly read an Rmd created by rstudio, # but we cannot expect all metadata to be there _assert_notebooks_equal(expected, converted_rstudio, @@ -307,23 +336,39 @@ def _test_rmarkdown_writer(basename): in the correct .Rmd + .nb.html files""" contents = _read_test_file(basename, 'notebook') expected = _read_test_file(basename, 'rmarkdown') - expected_rstudio = _read_test_file(basename, 'rmarkdown.rstudio') converted = convert(contents, to='rmarkdown', from_='notebook') # The writer must produce exactly the rmd/html we want assert converted['rmd'] == expected['rmd'] - assert converted['html'] == expected['html'] + assert converted['html'].strip() == expected['html'].strip() + + +def _diff_rmarkdown_writer_rstudio(basename): + """print a diff to the Rmd/.nb.html generated by rstudio + + We cannot expect to generate exactly the same result + as rstudio (minor things like markdown->HTML conversion. + Therefore, we do not use a test here. + + We want to have a diff for development purposes, though + """ + contents = _read_test_file(basename, 'notebook') + expected_rstudio = _read_test_file(basename, 'rmarkdown.rstudio') + converted = convert(contents, to='rmarkdown', from_='notebook') - # However, we cannot expect to generate exactly the same result - # as rstudio (minor things like markdown->HTML conversion. - # We want to have a diff for development purposes, though - print("Diff converted Rmd with {}.rmarkdown.rstudio.Rmd\n".format( - basename)) + print("\n\n" + "#########################################################\n" + "Diff converted Rmd with {}.rmarkdown.rstudio.Rmd\n" + "#########################################################" + "\n\n".format(basename)) print(_full_diff(converted['rmd'], expected_rstudio['rmd'])) print('\n\n') - print("Diff converted nb.html with {}.rmarkdown.rstudio.nb.html\n".format( - basename)) + print("\n\n" + "#########################################################\n" + "Diff converted nb.html with {}.rmarkdown.rstudio.nb.html\n" + "#########################################################" + "\n\n".format(basename)) print(_full_diff(converted['html'], expected_rstudio['html'])) print('\n\n') @@ -336,7 +381,7 @@ def _test_rmarkdown_rmarkdown(basename): converted = convert(notebook_dict, from_='notebook', to='rmarkdown') assert converted['rmd'] == contents['rmd'] - assert converted['html'] == contents['html'] + assert converted['html'].strip() == contents['html'].strip() def _test_notebook_notebook(basename): @@ -346,22 +391,28 @@ def _test_notebook_notebook(basename): rmarkdown = convert(contents, from_='notebook', to='rmarkdown') converted = convert(rmarkdown, from_='rmarkdown', to='notebook') - # TODO cell metadata? unimportant metadata(collapsed...) - _assert_notebooks_equal(contents, converted, check_cell_metadata=True) + _assert_notebooks_equal(contents, converted, check_notebook_metadata=True, + check_cell_metadata=True) def test_ex5_reader(): _test_rmarkdown_reader('ex5') + _test_rmarkdown_reader_rstudio('ex5') def test_ex5_writer(): _test_rmarkdown_writer('ex5') + _diff_rmarkdown_writer_rstudio('ex5') def test_ex5_rmarkdown_rmarkdown(): _test_rmarkdown_rmarkdown('ex5') +def test_ex5_notebook_notebook(): + _test_notebook_notebook('ex5') + + def test_ex6_reader(): _test_rmarkdown_reader('ex6') diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py index f12bbf5..a12b3af 100644 --- a/ipymd/lib/rmarkdown.py +++ b/ipymd/lib/rmarkdown.py @@ -3,6 +3,7 @@ import base64 import json from ipymd.ext.six import string_types +from html import escape as _html_escape try: import nbformat as nbf @@ -200,6 +201,11 @@ def done_merging(): return merged +def html_escape(s): + """escape HTML and double quotes only. """ + return _html_escape(s, quote=False).replace('"', """) + + class HtmlNbChunkCell(object): NO_CODE_FROM_HTMLNB = "Code is not parsed from .html.nb. " \ "Use code provided by *.Rmd instead. " @@ -229,6 +235,18 @@ def new_plot(self, mime, data, b64): metadata=meta) ) + def new_error(self, b64): + err_dict = {} if not b64 else _read_rmd_b64(b64) + traceback = [str(x) for x in err_dict.get("traceback", [])] + ename = err_dict.get("ename", "") + evalue = err_dict.get("evalue", "") + self._cell.outputs.append( + nbf.v4.new_output('error', + traceback=traceback, + ename=ename, + evalue=evalue) + ) + @property def cell(self): return self._cell diff --git a/ipymd/ressources/r_notebook.template.html b/ipymd/ressources/r_notebook.template.html index 53d4f8f..c9c5b68 100644 --- a/ipymd/ressources/r_notebook.template.html +++ b/ipymd/ressources/r_notebook.template.html @@ -11,7 +11,7 @@ -Example 5 +{{ title }} @@ -166,7 +166,7 @@ -

Example 5

+

{{ title }}

From 5b9101c5eaf96aed0b277ed9c8166fdef5556668 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 16 Nov 2017 16:06:03 +0100 Subject: [PATCH 21/33] Add test cases and related fixes: ex6 --- examples/ex5.rmarkdown.nb.html | 2 +- examples/ex6.notebook.ipynb | 919 ++++++++++++++++++++++++- examples/ex6.rmarkdown.Rmd | 60 +- examples/ex6.rmarkdown.nb.html | 729 +++++++++++++++++++- examples/ex6.rmarkdown.rstudio.Rmd | 39 -- examples/ex6.rmarkdown.rstudio.nb.html | 251 ------- ipymd/formats/rmarkdown.py | 35 +- ipymd/lib/rmarkdown.py | 6 +- 8 files changed, 1697 insertions(+), 344 deletions(-) delete mode 100644 examples/ex6.rmarkdown.rstudio.Rmd delete mode 100644 examples/ex6.rmarkdown.rstudio.nb.html diff --git a/examples/ex5.rmarkdown.nb.html b/examples/ex5.rmarkdown.nb.html index acd1e9c..2b7b520 100644 --- a/examples/ex5.rmarkdown.nb.html +++ b/examples/ex5.rmarkdown.nb.html @@ -188,7 +188,7 @@

Header

print("Hello world!")
- +
Hello world!
diff --git a/examples/ex6.notebook.ipynb b/examples/ex6.notebook.ipynb index b5a4404..a9a3a8c 100644 --- a/examples/ex6.notebook.ipynb +++ b/examples/ex6.notebook.ipynb @@ -4,9 +4,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# Python notebook tests\n", + "\n", + "Some literal python which is not evaluated: \n", + "\n", + "```python\n", + "print(\"Hello World!\")\n", + "```\n", + "\n", + "## Advanved Markdown\n", + "\n", "Foo bar kk\n", "\n", - "$\\sum_{i=1}^n 2^i$" + "$\\sum_{i=1}^n 2^i$\n", + "\n", + "## Special characters, text output" ] }, { @@ -36,15 +48,65 @@ " print(\"ä'<>$& \" + str(i))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Multiple images in the same output" + ] + }, { "cell_type": "code", "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\nrkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "n = 256\n", + "X = np.linspace(-np.pi,np.pi,n,endpoint=True)\n", + "Y = np.sin(2*X)\n", + "\n", + "fig, ax = plt.subplots( nrows=1, ncols=1 )\n", + "ax.plot (X, Y+1, color='blue', alpha=1.00)\n", + "ax.plot (X, Y-1, color='blue', alpha=1.00)\n", + "plt.show()\n", + "\n", + "fig, ax = plt.subplots( nrows=1, ncols=1 )\n", + "ax.plot (X, Y+1, color='red', alpha=1.00)\n", + "ax.plot (X, Y-1, color='red', alpha=1.00)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", "metadata": { + "collapsed": true }, - "outputs": [], "source": [ - "import seaborn as sns\n", - "%matplotlib inline" + "## A table" ] }, { @@ -54,19 +116,821 @@ "outputs": [ { "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
xy
000
112
224
336
448
5510
6612
7714
8816
9918
\n", + "
" + ], "text/plain": [ - "" + " x y\n", + "0 0 0\n", + "1 1 2\n", + "2 2 4\n", + "3 3 6\n", + "4 4 8\n", + "5 5 10\n", + "6 6 12\n", + "7 7 14\n", + "8 8 16\n", + "9 9 18" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" - }, + } + ], + "source": [ + "import pandas as pd\n", + "df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2))\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## svg images" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "%config InlineBackend.figure_format = 'svg'" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ { "data": { - "image/png": "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\n0MS/AXc8VdgXcacAAAAAAElFTkSuQmCC\n", + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -74,17 +938,42 @@ } ], "source": [ - "import seaborn as sns\n", - "sns.boxplot(range(10))" + "n = 256\n", + "X = np.linspace(-np.pi,np.pi,n,endpoint=True)\n", + "Y = np.sin(2*X)\n", + "\n", + "fig, ax = plt.subplots( nrows=1, ncols=1 )\n", + "ax.plot (X, Y+1, color='green', alpha=1.00)\n", + "ax.plot (X, Y-1, color='green', alpha=1.00)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## An Error" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": { - }, - "outputs": [], - "source": [] + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "ename": "SyntaxError", + "evalue": "invalid syntax (, line 3)", + "output_type": "error", + "traceback": [ + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b[0m\n\u001b[0;31m knitr::knit_engines$set(python = reticulate::eng_python)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + ] + } + ], + "source": [ + "library(knitr)\n", + "library(reticulate)\n", + "knitr::knit_engines$set(python = reticulate::eng_python)" + ] } ], "metadata": { diff --git a/examples/ex6.rmarkdown.Rmd b/examples/ex6.rmarkdown.Rmd index 76cb9f9..bfec50b 100644 --- a/examples/ex6.rmarkdown.Rmd +++ b/examples/ex6.rmarkdown.Rmd @@ -15,25 +15,77 @@ language_info: version: 3.6.2 --- +# Python notebook tests + +Some literal python which is not evaluated: + +```python +print("Hello World!") +``` + +## Advanved Markdown + Foo bar kk $\sum_{i=1}^n 2^i$ +## Special characters, text output + ```{python} for i in range(10): print("ä'<>$& " + str(i)) ``` +## Multiple images in the same output + ```{python} -import seaborn as sns -%matplotlib inline +import numpy as np +import matplotlib.pyplot as plt + +n = 256 +X = np.linspace(-np.pi,np.pi,n,endpoint=True) +Y = np.sin(2*X) + +fig, ax = plt.subplots( nrows=1, ncols=1 ) +ax.plot (X, Y+1, color='blue', alpha=1.00) +ax.plot (X, Y-1, color='blue', alpha=1.00) +plt.show() + +fig, ax = plt.subplots( nrows=1, ncols=1 ) +ax.plot (X, Y+1, color='red', alpha=1.00) +ax.plot (X, Y-1, color='red', alpha=1.00) +plt.show() ``` +## A table + ```{python} -import seaborn as sns -sns.boxplot(range(10)) +import pandas as pd +df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2)) +df +``` + +## svg images + +```{python, collapsed=TRUE} +%config InlineBackend.figure_format = 'svg' ``` ```{python} +n = 256 +X = np.linspace(-np.pi,np.pi,n,endpoint=True) +Y = np.sin(2*X) +fig, ax = plt.subplots( nrows=1, ncols=1 ) +ax.plot (X, Y+1, color='green', alpha=1.00) +ax.plot (X, Y-1, color='green', alpha=1.00) +plt.show() +``` + +## An Error + +```{python} +library(knitr) +library(reticulate) +knitr::knit_engines$set(python = reticulate::eng_python) ``` diff --git a/examples/ex6.rmarkdown.nb.html b/examples/ex6.rmarkdown.nb.html index 3f30d9d..843ba75 100644 --- a/examples/ex6.rmarkdown.nb.html +++ b/examples/ex6.rmarkdown.nb.html @@ -11,7 +11,7 @@ -Example 6 +IPYMD Notebook @@ -142,7 +142,7 @@ @@ -166,48 +166,737 @@ -

Example 6

+

IPYMD Notebook

+

Python notebook tests

+

Some literal python which is not evaluated:

+
print("Hello World!")
+

Advanved Markdown

Foo bar kk

-

\(\sum_{i=1}^n 2^i\)

+

$\sum_{i=1}^n 2^i$

+

Special characters, text output

- -
plot(1:10)
+ +
for i in range(10):
+    print("ä'<>$& " + str(i))
- -

- + +
ä'<>$& 0
+ä'<>$& 1
+ä'<>$& 2
+ä'<>$& 3
+ä'<>$& 4
+ä'<>$& 5
+ä'<>$& 6
+ä'<>$& 7
+ä'<>$& 8
+ä'<>$& 9
+ +

Multiple images in the same output

- -
import seaborn as sns
-%matplotlib inline
-from pylab import *
+ +
import numpy as np
+import matplotlib.pyplot as plt
+
+n = 256
+X = np.linspace(-np.pi,np.pi,n,endpoint=True)
+Y = np.sin(2*X)
+
+fig, ax = plt.subplots( nrows=1, ncols=1 )
+ax.plot (X, Y+1, color='blue', alpha=1.00)
+ax.plot (X, Y-1, color='blue', alpha=1.00)
+plt.show()
+
+fig, ax = plt.subplots( nrows=1, ncols=1 )
+ax.plot (X, Y+1, color='red', alpha=1.00)
+ax.plot (X, Y-1, color='red', alpha=1.00)
+plt.show()
+ +
+ + +

+ +
+ + +

+ + +

A table

+ + + +
import pandas as pd
+df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2))
+df
+ + +
   x   y
+0  0   0
+1  1   2
+2  2   4
+3  3   6
+4  4   8
+5  5  10
+6  6  12
+7  7  14
+8  8  16
+9  9  18
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
xy
000
112
224
336
448
5510
6612
7714
8816
9918
+
+

svg images

- -
import seaborn as sns
-sns.boxplot(range(10))
-figure()
-plot(range(10))
+ +
%config InlineBackend.figure_format = 'svg'
+ + +
n = 256
+X = np.linspace(-np.pi,np.pi,n,endpoint=True)
+Y = np.sin(2*X)
+
+fig, ax = plt.subplots( nrows=1, ncols=1 )
+ax.plot (X, Y+1, color='green', alpha=1.00)
+ax.plot (X, Y-1, color='green', alpha=1.00)
+plt.show()
+ + +
+ + +

\n', '\n', '\n', '\n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', '\n']" />

+ +

An Error

+ +
library(knitr)
+library(reticulate)
+knitr::knit_engines$set(python = reticulate::eng_python)
+ + +
  File "", line 3
+    knitr::knit_engines$set(python = reticulate::eng_python)
+          ^
+SyntaxError: invalid syntax
+ -
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDYKLS0tCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCiVtYXRwbG90bGliIGlubGluZQpmcm9tIHB5bGFiIGltcG9ydCAqCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCnNucy5ib3hwbG90KHJhbmdlKDEwKSkKZmlndXJlKCkKcGxvdChyYW5nZSgxMCkpCmBgYAoKYGBge3B5dGhvbn0KCmBgYAo=
+ +
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKLS0tCgojIFB5dGhvbiBub3RlYm9vayB0ZXN0cwoKU29tZSBsaXRlcmFsIHB5dGhvbiB3aGljaCBpcyBub3QgZXZhbHVhdGVkOgoKYGBgcHl0aG9uCnByaW50KCJIZWxsbyBXb3JsZCEiKQpgYGAKCiMjIEFkdmFudmVkIE1hcmtkb3duCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCiMjIFNwZWNpYWwgY2hhcmFjdGVycywgdGV4dCBvdXRwdXQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKIyMgTXVsdGlwbGUgaW1hZ2VzIGluIHRoZSBzYW1lIG91dHB1dAoKYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKCm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2JsdWUnLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdibHVlJywgYWxwaGE9MS4wMCkKcGx0LnNob3coKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCmF4LnBsb3QgKFgsIFktMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBIHRhYmxlCgpgYGB7cHl0aG9ufQppbXBvcnQgcGFuZGFzIGFzIHBkCmRmID0gcGQuRGF0YUZyYW1lKCkuYXNzaWduKHg9cmFuZ2UoMTApLCB5PXJhbmdlKDAsIDIwLCAyKSkKZGYKYGBgCgojIyBzdmcgaW1hZ2VzCgpgYGB7cHl0aG9uLCBjb2xsYXBzZWQ9VFJVRX0KJWNvbmZpZyBJbmxpbmVCYWNrZW5kLmZpZ3VyZV9mb3JtYXQgPSAnc3ZnJwpgYGAKCmBgYHtweXRob259Cm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2dyZWVuJywgYWxwaGE9MS4wMCkKYXgucGxvdCAoWCwgWS0xLCBjb2xvcj0nZ3JlZW4nLCBhbHBoYT0xLjAwKQpwbHQuc2hvdygpCmBgYAoKIyMgQW4gRXJyb3IKCmBgYHtweXRob259CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkocmV0aWN1bGF0ZSkKa25pdHI6OmtuaXRfZW5naW5lcyRzZXQocHl0aG9uID0gcmV0aWN1bGF0ZTo6ZW5nX3B5dGhvbikKYGBgCg==
@@ -248,4 +937,4 @@

Example 6

- + \ No newline at end of file diff --git a/examples/ex6.rmarkdown.rstudio.Rmd b/examples/ex6.rmarkdown.rstudio.Rmd deleted file mode 100644 index 76cb9f9..0000000 --- a/examples/ex6.rmarkdown.rstudio.Rmd +++ /dev/null @@ -1,39 +0,0 @@ ---- -kernelspec: - display_name: Python 3 - 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.6.2 ---- - -Foo bar kk - -$\sum_{i=1}^n 2^i$ - -```{python} -for i in range(10): - print("ä'<>$& " + str(i)) -``` - -```{python} -import seaborn as sns -%matplotlib inline -``` - -```{python} -import seaborn as sns -sns.boxplot(range(10)) -``` - -```{python} - -``` diff --git a/examples/ex6.rmarkdown.rstudio.nb.html b/examples/ex6.rmarkdown.rstudio.nb.html deleted file mode 100644 index 3f30d9d..0000000 --- a/examples/ex6.rmarkdown.rstudio.nb.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - - - - -Example 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - -

Foo bar kk

-

\(\sum_{i=1}^n 2^i\)

- - - -
plot(1:10)
- - -

- - - - - - -
import seaborn as sns
-%matplotlib inline
-from pylab import *
- - - - - - -
import seaborn as sns
-sns.boxplot(range(10))
-figure()
-plot(range(10))
- - - - - - - -
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKb3V0cHV0OiBodG1sX25vdGVib29rCnRpdGxlOiBFeGFtcGxlIDYKLS0tCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCiVtYXRwbG90bGliIGlubGluZQpmcm9tIHB5bGFiIGltcG9ydCAqCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IHNlYWJvcm4gYXMgc25zCnNucy5ib3hwbG90KHJhbmdlKDEwKSkKZmlndXJlKCkKcGxvdChyYW5nZSgxMCkpCmBgYAoKYGBge3B5dGhvbn0KCmBgYAo=
- - - -
- - - - - - - - diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index d830483..6430618 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -295,8 +295,9 @@ def _code_block(self, code, meta): def append_markdown(self, source, metadata): source = _ensure_string(source) if metadata is not None and len(metadata) > 0: - raise RuntimeWarning("Metadata for markdown cells is currently" - "not supported.") + print("WARNING: Metadata for markdown cells is currently " + "not supported.") + self._output.write(source.rstrip()) def _encode_metadata(self, metadata): @@ -397,6 +398,11 @@ def _create_output_tag(self, output): # then images... yield from self._create_output_tag_image(output) + if len(output.get('data', [])): + # if there are other mime types which could not be processed. + raise RuntimeError("Invalid output mime-types: {}".format( + ", ".join(output['data'].keys()))) + def _create_output_tag_error(self, output): try: traceback = output['traceback'] @@ -412,23 +418,28 @@ def _create_output_tag_error(self, output): pass def _create_output_tag_text(self, output): - try: - text = _ensure_string(output['data'].pop('text/plain')) - yield self._create_tag('output', - tag_content=self._format_text_output(text), - tag_meta={'data': text}) - except KeyError: - pass + # sorted, 'text/plain' first! + mime_callbacks = [ + ('text/plain', self._format_text_output), + ('text/html', lambda x: x) + ] + for mime, mime_callback in mime_callbacks: + try: + data = _ensure_string(output['data'].pop(mime)) + yield self._create_tag('output', + tag_content=mime_callback(data), + tag_meta={'mime': mime, 'data': data}) + except KeyError: + pass def _create_output_tag_image(self, output): - for mime, data in output['data'].items(): + for mime in list(output['data']): if mime.startswith('image/'): + data = output['data'].pop(mime) yield self._create_tag('plot', tag_content=self._format_image( mime, data), tag_meta=output['metadata']) - else: - raise RuntimeError("Invalid output mime-type: {}".format(mime)) @staticmethod def _format_error(traceback): diff --git a/ipymd/lib/rmarkdown.py b/ipymd/lib/rmarkdown.py index a12b3af..b4af96d 100644 --- a/ipymd/lib/rmarkdown.py +++ b/ipymd/lib/rmarkdown.py @@ -218,10 +218,12 @@ def __init__(self, execution_count): execution_count=self._count) def new_output(self, tag, b64): + meta = _read_rmd_b64(b64) + mime = meta.get('mime', 'text/plain') self._cell.outputs.append( + nbf.v4.new_output('execute_result', - {'text/plain': - _read_rmd_b64(b64)['data'].strip()}, + {mime: meta['data'].strip()}, execution_count=self._count, # metadata={"output_type": tag})) metadata={})) From 01c47e2c10ed6dc6e5f640119523ffc6dc101d0c Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Thu, 16 Nov 2017 16:43:26 +0100 Subject: [PATCH 22/33] Fix _assert_notebooks_equal. Missing output were ignored. Adapted unittests accordingly. --- ipymd/formats/tests/test_notebook.py | 18 ++++++++++-------- ipymd/lib/notebook.py | 20 ++++++++++++++------ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/ipymd/formats/tests/test_notebook.py b/ipymd/formats/tests/test_notebook.py index c0d0dcc..21c9683 100644 --- a/ipymd/formats/tests/test_notebook.py +++ b/ipymd/formats/tests/test_notebook.py @@ -23,24 +23,24 @@ def _test_notebook_reader(basename): assert converted == expected -def _test_notebook_writer(basename): +def _test_notebook_writer(basename, check_outputs=True): """Check that (test nb) and (test cells ==> nb) are the same. """ converted, expected = _test_writer(basename, 'notebook') - _assert_notebooks_equal(converted, expected, check_notebook_metadata=False) - # assert _compare_notebooks(converted, expected) + _assert_notebooks_equal(converted, expected, check_notebook_metadata=False, + check_cell_outputs=check_outputs) -def _test_notebook_notebook(basename): +def _test_notebook_notebook(basename, check_outputs=True): """Check that the double conversion is the identity.""" contents = _read_test_file(basename, 'notebook') cells = convert(contents, from_='notebook') converted = convert(cells, to='notebook') - _assert_notebooks_equal(contents, converted, check_notebook_metadata=False) - # assert _compare_notebooks(contents, converted) + _assert_notebooks_equal(contents, converted, check_notebook_metadata=False, + check_cell_outputs=check_outputs) def test_notebook_reader(): @@ -51,11 +51,13 @@ def test_notebook_reader(): def test_notebook_writer(): _test_notebook_writer('ex1') - _test_notebook_writer('ex2') + # Ex2 contains an image, which is not supported by ipymd internal format + _test_notebook_writer('ex2', check_outputs=False) _test_notebook_writer('ex3') def test_notebook_notebook(): _test_notebook_notebook('ex1') - _test_notebook_notebook('ex2') + # Ex2 contains an image, which is not supported by ipymd internal format + _test_notebook_notebook('ex2', check_outputs=False) _test_notebook_notebook('ex3') diff --git a/ipymd/lib/notebook.py b/ipymd/lib/notebook.py index de6b381..9172cea 100644 --- a/ipymd/lib/notebook.py +++ b/ipymd/lib/notebook.py @@ -47,20 +47,28 @@ def _assert_cell_outputs_equal(output_0, output_1, check_metadata=True): assert output_0['metadata'] == output_1['metadata'] -def _assert_cells_equal(cell_0, cell_1, check_metadata=True): +def _assert_cells_equal(cell_0, cell_1, check_metadata=True, + check_outputs=True): assert cell_0['cell_type'] == cell_1['cell_type'] assert _cell_source(cell_0) == _cell_source(cell_1) - for output_0, output_1 in zip(_cell_outputs(cell_0), _cell_outputs(cell_1)): - _assert_cell_outputs_equal(output_0, output_1, - check_metadata=check_metadata) + if check_outputs: + outputs_0 = _cell_outputs(cell_0) + outputs_1 = _cell_outputs(cell_1) + assert len(outputs_0) == len(outputs_1) + for output_0, output_1 in zip(outputs_0, outputs_1): + _assert_cell_outputs_equal(output_0, output_1, + check_metadata=check_metadata) def _assert_notebooks_equal(nb_0, nb_1, check_notebook_metadata=True, - check_cell_metadata=True): + check_cell_metadata=True, + check_cell_outputs=True): if check_notebook_metadata: assert nb_0['metadata'] == nb_1['metadata'] + assert len(nb_0['cells']) == len(nb_1['cells']) for cell_0, cell_1 in zip(nb_0['cells'], nb_1['cells']): - _assert_cells_equal(cell_0, cell_1, check_metadata=check_cell_metadata) + _assert_cells_equal(cell_0, cell_1, check_metadata=check_cell_metadata, + check_outputs=check_cell_outputs) def _cell_input(cell): From 20ef9fb1c071f6d7514619c93f37063fc411269e Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 17 Nov 2017 18:03:23 +0100 Subject: [PATCH 23/33] Ignore dev script in gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 946fb8c..87c019e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ dist/ pytest.ini venv sandbox + +# internal script for development +convert_testcases.sh From 790af0ca931247ab853591ff109c1f69ceb5fc25 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Fri, 17 Nov 2017 18:06:01 +0100 Subject: [PATCH 24/33] Fix remaining issues with ex6. As a part of this, the way how an <--rnb-output--> chunk is generated is entirely rewritten. All elements from the jupyter notebook are now contained in b64, while maintaining compatibility to rstudio. --- examples/ex5.rmarkdown.nb.html | 2 +- examples/ex6.notebook.ipynb | 68 +++--- examples/ex6.rmarkdown.Rmd | 7 +- examples/ex6.rmarkdown.nb.html | 132 ++---------- examples/ex7.notebook.ipynb | 288 ++++++++++++++++++++++++++ ipymd/formats/rmarkdown.py | 82 +++++--- ipymd/formats/tests/test_rmarkdown.py | 93 ++++++++- ipymd/lib/notebook.py | 28 ++- ipymd/lib/rmarkdown.py | 45 ++-- 9 files changed, 527 insertions(+), 218 deletions(-) create mode 100644 examples/ex7.notebook.ipynb diff --git a/examples/ex5.rmarkdown.nb.html b/examples/ex5.rmarkdown.nb.html index 2b7b520..6a96dd7 100644 --- a/examples/ex5.rmarkdown.nb.html +++ b/examples/ex5.rmarkdown.nb.html @@ -188,7 +188,7 @@

Header

print("Hello world!")
- +
Hello world!
diff --git a/examples/ex6.notebook.ipynb b/examples/ex6.notebook.ipynb index a9a3a8c..6cfd9f1 100644 --- a/examples/ex6.notebook.ipynb +++ b/examples/ex6.notebook.ipynb @@ -64,7 +64,7 @@ "data": { "image/png": "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\nrkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -74,7 +74,7 @@ "data": { "image/png": "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\nQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -223,23 +223,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## svg images" + "## SVG image" ] }, { "cell_type": "code", "execution_count": 4, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "%config InlineBackend.figure_format = 'svg'" - ] - }, - { - "cell_type": "code", - "execution_count": 5, "metadata": {}, "outputs": [ { @@ -279,10 +268,10 @@ " \n", " \n", + "\" id=\"mc9cd1f7f4e\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -336,7 +325,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -376,7 +365,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -405,7 +394,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -441,7 +430,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -454,7 +443,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -467,7 +456,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -484,10 +473,10 @@ " \n", " \n", + "\" id=\"mb73bb25897\" style=\"stroke:#000000;stroke-width:0.8;\"/>\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -511,7 +500,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -553,7 +542,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -569,7 +558,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -585,7 +574,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -600,7 +589,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -615,7 +604,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -630,7 +619,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -645,7 +634,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -659,7 +648,7 @@ " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -938,6 +927,7 @@ } ], "source": [ + "%config InlineBackend.figure_format = 'svg'\n", "n = 256\n", "X = np.linspace(-np.pi,np.pi,n,endpoint=True)\n", "Y = np.sin(2*X)\n", @@ -957,15 +947,15 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "ename": "SyntaxError", - "evalue": "invalid syntax (, line 3)", + "evalue": "invalid syntax (, line 3)", "output_type": "error", "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b[0m\n\u001b[0;31m knitr::knit_engines$set(python = reticulate::eng_python)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b[0m\n\u001b[0;31m knitr::knit_engines$set(python = reticulate::eng_python)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], diff --git a/examples/ex6.rmarkdown.Rmd b/examples/ex6.rmarkdown.Rmd index bfec50b..da46a46 100644 --- a/examples/ex6.rmarkdown.Rmd +++ b/examples/ex6.rmarkdown.Rmd @@ -65,13 +65,10 @@ df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2)) df ``` -## svg images - -```{python, collapsed=TRUE} -%config InlineBackend.figure_format = 'svg' -``` +## SVG image ```{python} +%config InlineBackend.figure_format = 'svg' n = 256 X = np.linspace(-np.pi,np.pi,n,endpoint=True) Y = np.sin(2*X) diff --git a/examples/ex6.rmarkdown.nb.html b/examples/ex6.rmarkdown.nb.html index 843ba75..f4206f9 100644 --- a/examples/ex6.rmarkdown.nb.html +++ b/examples/ex6.rmarkdown.nb.html @@ -174,7 +174,7 @@

IPYMD Notebook

Python notebook tests

Some literal python which is not evaluated:

-
print("Hello World!")
+
print("Hello World!")

Advanved Markdown

Foo bar kk

$\sum_{i=1}^n 2^i$

@@ -185,7 +185,7 @@

Special characters, text output

for i in range(10):
     print("ä'<>$& " + str(i))
- +
ä'<>$& 0
 ä'<>$& 1
 ä'<>$& 2
@@ -220,10 +220,7 @@ 

Multiple images in the same output

- -
- - +

Multiple images in the same output

- -
- - +

Multiple images in the same output

- +

A table

@@ -762,108 +756,16 @@

A table

df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2)) df
- -
   x   y
-0  0   0
-1  1   2
-2  2   4
-3  3   6
-4  4   8
-5  5  10
-6  6  12
-7  7  14
-8  8  16
-9  9  18
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
xy
000
112
224
336
448
5510
6612
7714
8816
9918
-
+ +['
\n', '\n', '\n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', '
xy
000
112
224
336
448
5510
6612
7714
8816
9918
\n', '
'] -

svg images

+

SVG image

- -
%config InlineBackend.figure_format = 'svg'
- - - - -
n = 256
+
+
%config InlineBackend.figure_format = 'svg'
+n = 256
 X = np.linspace(-np.pi,np.pi,n,endpoint=True)
 Y = np.sin(2*X)
 
@@ -872,11 +774,9 @@ 

svg images

ax.plot (X, Y-1, color='green', alpha=1.00) plt.show()
- -
+ +
['']
- -

\n', '\n', '\n', '\n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', '\n']" />

An Error

@@ -887,8 +787,8 @@

An Error

library(reticulate) knitr::knit_engines$set(python = reticulate::eng_python)
- -
  File "", line 3
+
+
  File "", line 3
     knitr::knit_engines$set(python = reticulate::eng_python)
           ^
 SyntaxError: invalid syntax
@@ -896,7 +796,7 @@

An Error

-
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKLS0tCgojIFB5dGhvbiBub3RlYm9vayB0ZXN0cwoKU29tZSBsaXRlcmFsIHB5dGhvbiB3aGljaCBpcyBub3QgZXZhbHVhdGVkOgoKYGBgcHl0aG9uCnByaW50KCJIZWxsbyBXb3JsZCEiKQpgYGAKCiMjIEFkdmFudmVkIE1hcmtkb3duCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCiMjIFNwZWNpYWwgY2hhcmFjdGVycywgdGV4dCBvdXRwdXQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKIyMgTXVsdGlwbGUgaW1hZ2VzIGluIHRoZSBzYW1lIG91dHB1dAoKYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKCm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2JsdWUnLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdibHVlJywgYWxwaGE9MS4wMCkKcGx0LnNob3coKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCmF4LnBsb3QgKFgsIFktMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBIHRhYmxlCgpgYGB7cHl0aG9ufQppbXBvcnQgcGFuZGFzIGFzIHBkCmRmID0gcGQuRGF0YUZyYW1lKCkuYXNzaWduKHg9cmFuZ2UoMTApLCB5PXJhbmdlKDAsIDIwLCAyKSkKZGYKYGBgCgojIyBzdmcgaW1hZ2VzCgpgYGB7cHl0aG9uLCBjb2xsYXBzZWQ9VFJVRX0KJWNvbmZpZyBJbmxpbmVCYWNrZW5kLmZpZ3VyZV9mb3JtYXQgPSAnc3ZnJwpgYGAKCmBgYHtweXRob259Cm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2dyZWVuJywgYWxwaGE9MS4wMCkKYXgucGxvdCAoWCwgWS0xLCBjb2xvcj0nZ3JlZW4nLCBhbHBoYT0xLjAwKQpwbHQuc2hvdygpCmBgYAoKIyMgQW4gRXJyb3IKCmBgYHtweXRob259CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkocmV0aWN1bGF0ZSkKa25pdHI6OmtuaXRfZW5naW5lcyRzZXQocHl0aG9uID0gcmV0aWN1bGF0ZTo6ZW5nX3B5dGhvbikKYGBgCg==
+
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKLS0tCgojIFB5dGhvbiBub3RlYm9vayB0ZXN0cwoKU29tZSBsaXRlcmFsIHB5dGhvbiB3aGljaCBpcyBub3QgZXZhbHVhdGVkOgoKYGBgcHl0aG9uCnByaW50KCJIZWxsbyBXb3JsZCEiKQpgYGAKCiMjIEFkdmFudmVkIE1hcmtkb3duCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCiMjIFNwZWNpYWwgY2hhcmFjdGVycywgdGV4dCBvdXRwdXQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKIyMgTXVsdGlwbGUgaW1hZ2VzIGluIHRoZSBzYW1lIG91dHB1dAoKYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKCm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2JsdWUnLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdibHVlJywgYWxwaGE9MS4wMCkKcGx0LnNob3coKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCmF4LnBsb3QgKFgsIFktMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBIHRhYmxlCgpgYGB7cHl0aG9ufQppbXBvcnQgcGFuZGFzIGFzIHBkCmRmID0gcGQuRGF0YUZyYW1lKCkuYXNzaWduKHg9cmFuZ2UoMTApLCB5PXJhbmdlKDAsIDIwLCAyKSkKZGYKYGBgCgojIyBTVkcgaW1hZ2UKCmBgYHtweXRob259CiVjb25maWcgSW5saW5lQmFja2VuZC5maWd1cmVfZm9ybWF0ID0gJ3N2ZycKbiA9IDI1NgpYID0gbnAubGluc3BhY2UoLW5wLnBpLG5wLnBpLG4sZW5kcG9pbnQ9VHJ1ZSkKWSA9IG5wLnNpbigyKlgpCgpmaWcsIGF4ID0gcGx0LnN1YnBsb3RzKCBucm93cz0xLCBuY29scz0xICkKYXgucGxvdCAoWCwgWSsxLCBjb2xvcj0nZ3JlZW4nLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdncmVlbicsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBbiBFcnJvcgoKYGBge3B5dGhvbn0KbGlicmFyeShrbml0cikKbGlicmFyeShyZXRpY3VsYXRlKQprbml0cjo6a25pdF9lbmdpbmVzJHNldChweXRob24gPSByZXRpY3VsYXRlOjplbmdfcHl0aG9uKQpgYGAK
diff --git a/examples/ex7.notebook.ipynb b/examples/ex7.notebook.ipynb new file mode 100644 index 0000000..0ae2d27 --- /dev/null +++ b/examples/ex7.notebook.ipynb @@ -0,0 +1,288 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## R notebook with R kernel\n", + "\n", + "Some literal R code which should not be evaluated\n", + "```r\n", + "stop(\"not evaluated\")\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "library(tidyverse)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
    \n", + "\t
  1. 2
  2. \n", + "\t
  3. 4
  4. \n", + "\t
  5. 6
  6. \n", + "\t
  7. 8
  8. \n", + "\t
  9. 10
  10. \n", + "\t
  11. 12
  12. \n", + "\t
  13. 14
  14. \n", + "\t
  15. 16
  16. \n", + "\t
  17. 18
  18. \n", + "\t
  19. 20
  20. \n", + "\t
  21. 22
  22. \n", + "\t
  23. 24
  24. \n", + "\t
  25. 26
  26. \n", + "\t
  27. 28
  28. \n", + "\t
  29. 30
  30. \n", + "\t
  31. 32
  32. \n", + "\t
  33. 34
  34. \n", + "\t
  35. 36
  36. \n", + "\t
  37. 38
  38. \n", + "\t
  39. 40
  40. \n", + "
\n" + ], + "text/latex": [ + "\\begin{enumerate*}\n", + "\\item 2\n", + "\\item 4\n", + "\\item 6\n", + "\\item 8\n", + "\\item 10\n", + "\\item 12\n", + "\\item 14\n", + "\\item 16\n", + "\\item 18\n", + "\\item 20\n", + "\\item 22\n", + "\\item 24\n", + "\\item 26\n", + "\\item 28\n", + "\\item 30\n", + "\\item 32\n", + "\\item 34\n", + "\\item 36\n", + "\\item 38\n", + "\\item 40\n", + "\\end{enumerate*}\n" + ], + "text/markdown": [ + "1. 2\n", + "2. 4\n", + "3. 6\n", + "4. 8\n", + "5. 10\n", + "6. 12\n", + "7. 14\n", + "8. 16\n", + "9. 18\n", + "10. 20\n", + "11. 22\n", + "12. 24\n", + "13. 26\n", + "14. 28\n", + "15. 30\n", + "16. 32\n", + "17. 34\n", + "18. 36\n", + "19. 38\n", + "20. 40\n", + "\n", + "\n" + ], + "text/plain": [ + " [1] 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "data = 1:20 * 2\n", + "data" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\n", + "
xdata
1 2
2 4
3 6
4 8
510
612
714
816
918
1020
1122
1224
1326
1428
1530
1632
1734
1836
1938
2040
\n" + ], + "text/latex": [ + "\\begin{tabular}{r|ll}\n", + " x & data\\\\\n", + "\\hline\n", + "\t 1 & 2\\\\\n", + "\t 2 & 4\\\\\n", + "\t 3 & 6\\\\\n", + "\t 4 & 8\\\\\n", + "\t 5 & 10\\\\\n", + "\t 6 & 12\\\\\n", + "\t 7 & 14\\\\\n", + "\t 8 & 16\\\\\n", + "\t 9 & 18\\\\\n", + "\t 10 & 20\\\\\n", + "\t 11 & 22\\\\\n", + "\t 12 & 24\\\\\n", + "\t 13 & 26\\\\\n", + "\t 14 & 28\\\\\n", + "\t 15 & 30\\\\\n", + "\t 16 & 32\\\\\n", + "\t 17 & 34\\\\\n", + "\t 18 & 36\\\\\n", + "\t 19 & 38\\\\\n", + "\t 20 & 40\\\\\n", + "\\end{tabular}\n" + ], + "text/markdown": [ + "\n", + "x | data | \n", + "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n", + "| 1 | 2 | \n", + "| 2 | 4 | \n", + "| 3 | 6 | \n", + "| 4 | 8 | \n", + "| 5 | 10 | \n", + "| 6 | 12 | \n", + "| 7 | 14 | \n", + "| 8 | 16 | \n", + "| 9 | 18 | \n", + "| 10 | 20 | \n", + "| 11 | 22 | \n", + "| 12 | 24 | \n", + "| 13 | 26 | \n", + "| 14 | 28 | \n", + "| 15 | 30 | \n", + "| 16 | 32 | \n", + "| 17 | 34 | \n", + "| 18 | 36 | \n", + "| 19 | 38 | \n", + "| 20 | 40 | \n", + "\n", + "\n" + ], + "text/plain": [ + " x data\n", + "1 1 2 \n", + "2 2 4 \n", + "3 3 6 \n", + "4 4 8 \n", + "5 5 10 \n", + "6 6 12 \n", + "7 7 14 \n", + "8 8 16 \n", + "9 9 18 \n", + "10 10 20 \n", + "11 11 22 \n", + "12 12 24 \n", + "13 13 26 \n", + "14 14 28 \n", + "15 15 30 \n", + "16 16 32 \n", + "17 17 34 \n", + "18 18 36 \n", + "19 19 38 \n", + "20 20 40 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df = tibble(x=1:20, data=data)\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": {}, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAMAAADKOT/pAAACWFBMVEUAAAABAQEDAwMEBAQF\nBQUGBgYHBwcJCQkKCgoNDQ0PDw8QEBARERESEhITExMVFRUWFhYXFxcZGRkbGxscHBwfHx8g\nICAhISEmJiYnJycrKystLS0yMjIzMzM1NTU4ODg5OTk7OztBQUFCQkJDQ0NFRUVGRkZKSkpM\nTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5f\nX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZoaGhpaWlqampra2tsbGxubm5vb29ycnJzc3N1dXV2\ndnZ3d3d4eHh5eXl6enp8fHx/f3+AgICCgoKDg4OFhYWGhoaHh4eJiYmLi4uMjIyNjY2Ojo6P\nj4+QkJCRkZGTk5OUlJSVlZWWlpaXl5eZmZmampqbm5ucnJydnZ2enp6fn5+goKChoaGioqKj\no6OkpKSmpqaoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrKzs7O0tLS1tbW2tra3t7e4\nuLi5ubm6urq7u7u8vLy9vb2+vr6/v7/AwMDBwcHCwsLDw8PExMTFxcXGxsbHx8fIyMjJycnK\nysrLy8vMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT09PU1NTW1tbX19fY2NjZ2dna2trb29vc3Nzd\n3d3f39/g4ODh4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w\n8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7////IoxbwAAAA\nCXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nO2d/Z9d1XWfx05a9z1t+pqmJG1St71CqMIm\nOCpFsWlLmpS4hZi3pA2ucZMUYhqDIXZK7cSExGlMS0qoIsuWQFVFIlwsZAkhgTRIM3PPv9U7\ns7UuXDxaa9/v2Wdmn3Oe54erl883ey99135gBMhZaQCgNSu7PQDAEEAkgAIgEkABEAmgAIgE\nUABEAigAIgEUAJEACtBCpDffWGDjyhud8Nblbs69vHGxm4Ovnu/mXAq+Rk0FlxDpje++l7PN\n1e92wsXL3Zx7uXmzm4PXz3ZyLAUbNRWMSOPYcw4UnEAkjVHsOQcKTiCSxij2nAMFJxBJYxR7\n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\nkQAKgEgABfj/eVKPyALu1jYAAAAASUVORK5CYII=", + "text/plain": [ + "plot without title" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ggplot(df, aes(x=x, y=data)) + geom_bar(stat='identity')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "3.4.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 6430618..9e068a4 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -33,6 +33,7 @@ from .markdown import BaseMarkdownReader, BaseMarkdownWriter from ipymd.core.format_manager import convert from ..ext.six import StringIO +from collections import OrderedDict # ------------------------------------------------------------------------------ @@ -361,7 +362,8 @@ def template(self): def append_markdown(self, markdown, metadata): markdown = _ensure_string(markdown) html = pypandoc.convert_text(markdown, 'html', format='md') - self._output.write(self._create_tag('text', html, metadata) + "\n") + # ignore metadata, not supported. + self._output.write(self._create_tag('text', html) + "\n") def append_code(self, source, outputs, metadata): source = _ensure_string(source) @@ -384,7 +386,19 @@ def append_code(self, source, outputs, metadata): ) def _create_output_tag(self, output): - """yield tags such as \n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%config InlineBackend.figure_format = 'svg'\n", - "n = 256\n", - "X = np.linspace(-np.pi,np.pi,n,endpoint=True)\n", - "Y = np.sin(2*X)\n", - "\n", - "fig, ax = plt.subplots( nrows=1, ncols=1 )\n", - "ax.plot (X, Y+1, color='green', alpha=1.00)\n", - "ax.plot (X, Y-1, color='green', alpha=1.00)\n", - "plt.show()" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -947,15 +228,15 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { "ename": "SyntaxError", - "evalue": "invalid syntax (, line 3)", + "evalue": "invalid syntax (, line 3)", "output_type": "error", "traceback": [ - "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b[0m\n\u001b[0;31m knitr::knit_engines$set(python = reticulate::eng_python)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" + "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m3\u001b[0m\n\u001b[0;31m knitr::knit_engines$set(python = reticulate::eng_python)\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" ] } ], @@ -982,7 +263,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.2" + "version": "3.6.3" } }, "nbformat": 4, diff --git a/examples/ex6.rmarkdown.Rmd b/examples/ex6.rmarkdown.Rmd index da46a46..f1ea095 100644 --- a/examples/ex6.rmarkdown.Rmd +++ b/examples/ex6.rmarkdown.Rmd @@ -12,7 +12,7 @@ language_info: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.6.2 + version: 3.6.3 --- # Python notebook tests @@ -65,20 +65,6 @@ df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2)) df ``` -## SVG image - -```{python} -%config InlineBackend.figure_format = 'svg' -n = 256 -X = np.linspace(-np.pi,np.pi,n,endpoint=True) -Y = np.sin(2*X) - -fig, ax = plt.subplots( nrows=1, ncols=1 ) -ax.plot (X, Y+1, color='green', alpha=1.00) -ax.plot (X, Y-1, color='green', alpha=1.00) -plt.show() -``` - ## An Error ```{python} diff --git a/examples/ex6.rmarkdown.nb.html b/examples/ex6.rmarkdown.nb.html index f4206f9..46c1e6a 100644 --- a/examples/ex6.rmarkdown.nb.html +++ b/examples/ex6.rmarkdown.nb.html @@ -220,7 +220,7 @@

Multiple images in the same output

- +

Multiple images in the same output

- +

A table df = pd.DataFrame().assign(x=range(10), y=range(0, 20, 2)) df - -['

\n', '\n', '\n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', '
xy
000
112
224
336
448
5510
6612
7714
8816
9918
\n', '
'] - - -

SVG image

- - - -
%config InlineBackend.figure_format = 'svg'
-n = 256
-X = np.linspace(-np.pi,np.pi,n,endpoint=True)
-Y = np.sin(2*X)
-
-fig, ax = plt.subplots( nrows=1, ncols=1 )
-ax.plot (X, Y+1, color='green', alpha=1.00)
-ax.plot (X, Y-1, color='green', alpha=1.00)
-plt.show()
- - -
['']
- + +['
\n', '\n', '\n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', ' \n', '
xy
000
112
224
336
448
5510
6612
7714
8816
9918
\n', '
']

An Error

@@ -787,8 +768,8 @@

An Error

library(reticulate) knitr::knit_engines$set(python = reticulate::eng_python) - -
  File "", line 3
+
+
  File "", line 3
     knitr::knit_engines$set(python = reticulate::eng_python)
           ^
 SyntaxError: invalid syntax
@@ -796,7 +777,7 @@

An Error

-
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjIKLS0tCgojIFB5dGhvbiBub3RlYm9vayB0ZXN0cwoKU29tZSBsaXRlcmFsIHB5dGhvbiB3aGljaCBpcyBub3QgZXZhbHVhdGVkOgoKYGBgcHl0aG9uCnByaW50KCJIZWxsbyBXb3JsZCEiKQpgYGAKCiMjIEFkdmFudmVkIE1hcmtkb3duCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCiMjIFNwZWNpYWwgY2hhcmFjdGVycywgdGV4dCBvdXRwdXQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKIyMgTXVsdGlwbGUgaW1hZ2VzIGluIHRoZSBzYW1lIG91dHB1dAoKYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKCm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2JsdWUnLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdibHVlJywgYWxwaGE9MS4wMCkKcGx0LnNob3coKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCmF4LnBsb3QgKFgsIFktMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBIHRhYmxlCgpgYGB7cHl0aG9ufQppbXBvcnQgcGFuZGFzIGFzIHBkCmRmID0gcGQuRGF0YUZyYW1lKCkuYXNzaWduKHg9cmFuZ2UoMTApLCB5PXJhbmdlKDAsIDIwLCAyKSkKZGYKYGBgCgojIyBTVkcgaW1hZ2UKCmBgYHtweXRob259CiVjb25maWcgSW5saW5lQmFja2VuZC5maWd1cmVfZm9ybWF0ID0gJ3N2ZycKbiA9IDI1NgpYID0gbnAubGluc3BhY2UoLW5wLnBpLG5wLnBpLG4sZW5kcG9pbnQ9VHJ1ZSkKWSA9IG5wLnNpbigyKlgpCgpmaWcsIGF4ID0gcGx0LnN1YnBsb3RzKCBucm93cz0xLCBuY29scz0xICkKYXgucGxvdCAoWCwgWSsxLCBjb2xvcj0nZ3JlZW4nLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdncmVlbicsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBbiBFcnJvcgoKYGBge3B5dGhvbn0KbGlicmFyeShrbml0cikKbGlicmFyeShyZXRpY3VsYXRlKQprbml0cjo6a25pdF9lbmdpbmVzJHNldChweXRob24gPSByZXRpY3VsYXRlOjplbmdfcHl0aG9uKQpgYGAK
+
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBQeXRob24gMwogIGxhbmd1YWdlOiBweXRob24KICBuYW1lOiBweXRob24zCmxhbmd1YWdlX2luZm86CiAgY29kZW1pcnJvcl9tb2RlOgogICAgbmFtZTogaXB5dGhvbgogICAgdmVyc2lvbjogMwogIGZpbGVfZXh0ZW5zaW9uOiAucHkKICBtaW1ldHlwZTogdGV4dC94LXB5dGhvbgogIG5hbWU6IHB5dGhvbgogIG5iY29udmVydF9leHBvcnRlcjogcHl0aG9uCiAgcHlnbWVudHNfbGV4ZXI6IGlweXRob24zCiAgdmVyc2lvbjogMy42LjMKLS0tCgojIFB5dGhvbiBub3RlYm9vayB0ZXN0cwoKU29tZSBsaXRlcmFsIHB5dGhvbiB3aGljaCBpcyBub3QgZXZhbHVhdGVkOgoKYGBgcHl0aG9uCnByaW50KCJIZWxsbyBXb3JsZCEiKQpgYGAKCiMjIEFkdmFudmVkIE1hcmtkb3duCgpGb28gYmFyIGtrCgokXHN1bV97aT0xfV5uIDJeaSQKCiMjIFNwZWNpYWwgY2hhcmFjdGVycywgdGV4dCBvdXRwdXQKCmBgYHtweXRob259CmZvciBpIGluIHJhbmdlKDEwKToKICAgIHByaW50KCLDpCc8PiQmICIgKyBzdHIoaSkpCmBgYAoKIyMgTXVsdGlwbGUgaW1hZ2VzIGluIHRoZSBzYW1lIG91dHB1dAoKYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQKCm4gPSAyNTYKWCA9IG5wLmxpbnNwYWNlKC1ucC5waSxucC5waSxuLGVuZHBvaW50PVRydWUpClkgPSBucC5zaW4oMipYKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J2JsdWUnLCBhbHBoYT0xLjAwKQpheC5wbG90IChYLCBZLTEsIGNvbG9yPSdibHVlJywgYWxwaGE9MS4wMCkKcGx0LnNob3coKQoKZmlnLCBheCA9IHBsdC5zdWJwbG90cyggbnJvd3M9MSwgbmNvbHM9MSApCmF4LnBsb3QgKFgsIFkrMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCmF4LnBsb3QgKFgsIFktMSwgY29sb3I9J3JlZCcsIGFscGhhPTEuMDApCnBsdC5zaG93KCkKYGBgCgojIyBBIHRhYmxlCgpgYGB7cHl0aG9ufQppbXBvcnQgcGFuZGFzIGFzIHBkCmRmID0gcGQuRGF0YUZyYW1lKCkuYXNzaWduKHg9cmFuZ2UoMTApLCB5PXJhbmdlKDAsIDIwLCAyKSkKZGYKYGBgCgojIyBBbiBFcnJvcgoKYGBge3B5dGhvbn0KbGlicmFyeShrbml0cikKbGlicmFyeShyZXRpY3VsYXRlKQprbml0cjo6a25pdF9lbmdpbmVzJHNldChweXRob24gPSByZXRpY3VsYXRlOjplbmdfcHl0aG9uKQpgYGAK
From 1834eed3a174724684ff681b190e7d4d8ceac434 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 18 Nov 2017 19:21:18 +0100 Subject: [PATCH 27/33] Add example7 (r notebook with R kernel). Fixed some minor issues in rmarkdown format. --- examples/ex7.notebook.ipynb | 371 +++++-------------- examples/ex7.rmarkdown.Rmd | 39 ++ examples/ex7.rmarkdown.nb.html | 509 ++++++++++++++++++++++++++ ipymd/formats/rmarkdown.py | 6 +- ipymd/formats/tests/test_rmarkdown.py | 16 + 5 files changed, 662 insertions(+), 279 deletions(-) create mode 100644 examples/ex7.rmarkdown.Rmd create mode 100644 examples/ex7.rmarkdown.nb.html diff --git a/examples/ex7.notebook.ipynb b/examples/ex7.notebook.ipynb index 0ae2d27..992fd9b 100644 --- a/examples/ex7.notebook.ipynb +++ b/examples/ex7.notebook.ipynb @@ -1,288 +1,103 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## R notebook with R kernel\n", - "\n", - "Some literal R code which should not be evaluated\n", - "```r\n", - "stop(\"not evaluated\")\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "library(tidyverse)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ + "cells": [ { - "data": { - "text/html": [ - "
    \n", - "\t
  1. 2
  2. \n", - "\t
  3. 4
  4. \n", - "\t
  5. 6
  6. \n", - "\t
  7. 8
  8. \n", - "\t
  9. 10
  10. \n", - "\t
  11. 12
  12. \n", - "\t
  13. 14
  14. \n", - "\t
  15. 16
  16. \n", - "\t
  17. 18
  18. \n", - "\t
  19. 20
  20. \n", - "\t
  21. 22
  22. \n", - "\t
  23. 24
  24. \n", - "\t
  25. 26
  26. \n", - "\t
  27. 28
  28. \n", - "\t
  29. 30
  30. \n", - "\t
  31. 32
  32. \n", - "\t
  33. 34
  34. \n", - "\t
  35. 36
  36. \n", - "\t
  37. 38
  38. \n", - "\t
  39. 40
  40. \n", - "
\n" - ], - "text/latex": [ - "\\begin{enumerate*}\n", - "\\item 2\n", - "\\item 4\n", - "\\item 6\n", - "\\item 8\n", - "\\item 10\n", - "\\item 12\n", - "\\item 14\n", - "\\item 16\n", - "\\item 18\n", - "\\item 20\n", - "\\item 22\n", - "\\item 24\n", - "\\item 26\n", - "\\item 28\n", - "\\item 30\n", - "\\item 32\n", - "\\item 34\n", - "\\item 36\n", - "\\item 38\n", - "\\item 40\n", - "\\end{enumerate*}\n" - ], - "text/markdown": [ - "1. 2\n", - "2. 4\n", - "3. 6\n", - "4. 8\n", - "5. 10\n", - "6. 12\n", - "7. 14\n", - "8. 16\n", - "9. 18\n", - "10. 20\n", - "11. 22\n", - "12. 24\n", - "13. 26\n", - "14. 28\n", - "15. 30\n", - "16. 32\n", - "17. 34\n", - "18. 36\n", - "19. 38\n", - "20. 40\n", - "\n", - "\n" - ], - "text/plain": [ - " [1] 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "data = 1:20 * 2\n", - "data" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ + "cell_type": "markdown", + "metadata": {}, + "source": "## R notebook with R kernel\n\nSome literal R code which should not be evaluated\n```r\nstop(\"not evaluated\")\n```" + }, { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\n", - "
xdata
1 2
2 4
3 6
4 8
510
612
714
816
918
1020
1122
1224
1326
1428
1530
1632
1734
1836
1938
2040
\n" + "cell_type": "code", + "execution_count": 1, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/plain": "Loading tidyverse: ggplot2\nLoading tidyverse: tibble\nLoading tidyverse: tidyr\nLoading tidyverse: readr\nLoading tidyverse: purrr\nLoading tidyverse: dplyr\nConflicts with tidy packages ---------------------------------------------------\nfilter(): dplyr, stats\nlag(): dplyr, stats" + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } ], - "text/latex": [ - "\\begin{tabular}{r|ll}\n", - " x & data\\\\\n", - "\\hline\n", - "\t 1 & 2\\\\\n", - "\t 2 & 4\\\\\n", - "\t 3 & 6\\\\\n", - "\t 4 & 8\\\\\n", - "\t 5 & 10\\\\\n", - "\t 6 & 12\\\\\n", - "\t 7 & 14\\\\\n", - "\t 8 & 16\\\\\n", - "\t 9 & 18\\\\\n", - "\t 10 & 20\\\\\n", - "\t 11 & 22\\\\\n", - "\t 12 & 24\\\\\n", - "\t 13 & 26\\\\\n", - "\t 14 & 28\\\\\n", - "\t 15 & 30\\\\\n", - "\t 16 & 32\\\\\n", - "\t 17 & 34\\\\\n", - "\t 18 & 36\\\\\n", - "\t 19 & 38\\\\\n", - "\t 20 & 40\\\\\n", - "\\end{tabular}\n" - ], - "text/markdown": [ - "\n", - "x | data | \n", - "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n", - "| 1 | 2 | \n", - "| 2 | 4 | \n", - "| 3 | 6 | \n", - "| 4 | 8 | \n", - "| 5 | 10 | \n", - "| 6 | 12 | \n", - "| 7 | 14 | \n", - "| 8 | 16 | \n", - "| 9 | 18 | \n", - "| 10 | 20 | \n", - "| 11 | 22 | \n", - "| 12 | 24 | \n", - "| 13 | 26 | \n", - "| 14 | 28 | \n", - "| 15 | 30 | \n", - "| 16 | 32 | \n", - "| 17 | 34 | \n", - "| 18 | 36 | \n", - "| 19 | 38 | \n", - "| 20 | 40 | \n", - "\n", - "\n" + "source": "library(tidyverse)" + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/html": "
    \n\t
  1. 2
  2. \n\t
  3. 4
  4. \n\t
  5. 6
  6. \n\t
  7. 8
  8. \n\t
  9. 10
  10. \n\t
  11. 12
  12. \n\t
  13. 14
  14. \n\t
  15. 16
  16. \n\t
  17. 18
  18. \n\t
  19. 20
  20. \n\t
  21. 22
  22. \n\t
  23. 24
  24. \n\t
  25. 26
  26. \n\t
  27. 28
  28. \n\t
  29. 30
  30. \n\t
  31. 32
  32. \n\t
  33. 34
  34. \n\t
  35. 36
  36. \n\t
  37. 38
  38. \n\t
  39. 40
  40. \n
\n", + "text/latex": "\\begin{enumerate*}\n\\item 2\n\\item 4\n\\item 6\n\\item 8\n\\item 10\n\\item 12\n\\item 14\n\\item 16\n\\item 18\n\\item 20\n\\item 22\n\\item 24\n\\item 26\n\\item 28\n\\item 30\n\\item 32\n\\item 34\n\\item 36\n\\item 38\n\\item 40\n\\end{enumerate*}\n", + "text/markdown": "1. 2\n2. 4\n3. 6\n4. 8\n5. 10\n6. 12\n7. 14\n8. 16\n9. 18\n10. 20\n11. 22\n12. 24\n13. 26\n14. 28\n15. 30\n16. 32\n17. 34\n18. 36\n19. 38\n20. 40\n\n\n", + "text/plain": " [1] 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40" + }, + "metadata": {}, + "output_type": "display_data" + } ], - "text/plain": [ - " x data\n", - "1 1 2 \n", - "2 2 4 \n", - "3 3 6 \n", - "4 4 8 \n", - "5 5 10 \n", - "6 6 12 \n", - "7 7 14 \n", - "8 8 16 \n", - "9 9 18 \n", - "10 10 20 \n", - "11 11 22 \n", - "12 12 24 \n", - "13 13 26 \n", - "14 14 28 \n", - "15 15 30 \n", - "16 16 32 \n", - "17 17 34 \n", - "18 18 36 \n", - "19 19 38 \n", - "20 20 40 " - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "df = tibble(x=1:20, data=data)\n", - "df" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ + "source": "data = 1:20 * 2\ndata" + }, { - "data": {}, - "metadata": {}, - "output_type": "display_data" + "cell_type": "code", + "execution_count": 3, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "text/html": "\n\n\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\t\n\n
xdata
1 2
2 4
3 6
4 8
510
612
714
816
918
1020
1122
1224
1326
1428
1530
1632
1734
1836
1938
2040
\n", + "text/latex": "\\begin{tabular}{r|ll}\n x & data\\\\\n\\hline\n\t 1 & 2\\\\\n\t 2 & 4\\\\\n\t 3 & 6\\\\\n\t 4 & 8\\\\\n\t 5 & 10\\\\\n\t 6 & 12\\\\\n\t 7 & 14\\\\\n\t 8 & 16\\\\\n\t 9 & 18\\\\\n\t 10 & 20\\\\\n\t 11 & 22\\\\\n\t 12 & 24\\\\\n\t 13 & 26\\\\\n\t 14 & 28\\\\\n\t 15 & 30\\\\\n\t 16 & 32\\\\\n\t 17 & 34\\\\\n\t 18 & 36\\\\\n\t 19 & 38\\\\\n\t 20 & 40\\\\\n\\end{tabular}\n", + "text/markdown": "\nx | data | \n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| 1 | 2 | \n| 2 | 4 | \n| 3 | 6 | \n| 4 | 8 | \n| 5 | 10 | \n| 6 | 12 | \n| 7 | 14 | \n| 8 | 16 | \n| 9 | 18 | \n| 10 | 20 | \n| 11 | 22 | \n| 12 | 24 | \n| 13 | 26 | \n| 14 | 28 | \n| 15 | 30 | \n| 16 | 32 | \n| 17 | 34 | \n| 18 | 36 | \n| 19 | 38 | \n| 20 | 40 | \n\n\n", + "text/plain": " x data\n1 1 2 \n2 2 4 \n3 3 6 \n4 4 8 \n5 5 10 \n6 6 12 \n7 7 14 \n8 8 16 \n9 9 18 \n10 10 20 \n11 11 22 \n12 12 24 \n13 13 26 \n14 14 28 \n15 15 30 \n16 16 32 \n17 17 34 \n18 18 36 \n19 19 38 \n20 20 40 " + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": "df = tibble(x=1:20, data=data)\ndf" }, { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAMAAADKOT/pAAACWFBMVEUAAAABAQEDAwMEBAQF\nBQUGBgYHBwcJCQkKCgoNDQ0PDw8QEBARERESEhITExMVFRUWFhYXFxcZGRkbGxscHBwfHx8g\nICAhISEmJiYnJycrKystLS0yMjIzMzM1NTU4ODg5OTk7OztBQUFCQkJDQ0NFRUVGRkZKSkpM\nTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5f\nX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZoaGhpaWlqampra2tsbGxubm5vb29ycnJzc3N1dXV2\ndnZ3d3d4eHh5eXl6enp8fHx/f3+AgICCgoKDg4OFhYWGhoaHh4eJiYmLi4uMjIyNjY2Ojo6P\nj4+QkJCRkZGTk5OUlJSVlZWWlpaXl5eZmZmampqbm5ucnJydnZ2enp6fn5+goKChoaGioqKj\no6OkpKSmpqaoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrKzs7O0tLS1tbW2tra3t7e4\nuLi5ubm6urq7u7u8vLy9vb2+vr6/v7/AwMDBwcHCwsLDw8PExMTFxcXGxsbHx8fIyMjJycnK\nysrLy8vMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT09PU1NTW1tbX19fY2NjZ2dna2trb29vc3Nzd\n3d3f39/g4ODh4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w\n8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7////IoxbwAAAA\nCXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nO2d/Z9d1XWfx05a9z1t+pqmJG1St71CqMIm\nOCpFsWlLmpS4hZi3pA2ucZMUYhqDIXZK7cSExGlMS0qoIsuWQFVFIlwsZAkhgTRIM3PPv9U7\ns7UuXDxaa9/v2Wdmn3Oe54erl883ey99135gBMhZaQCgNSu7PQDAEEAkgAIgEkABEAmgAIgE\nUABEAigAIgEUAJEACtBCpDffWGDjyhud8Nblbs69vHGxm4Ovnu/mXAq+Rk0FlxDpje++l7PN\n1e92wsXL3Zx7uXmzm4PXz3ZyLAUbNRWMSOPYcw4UnEAkjVHsOQcKTiCSxij2nAMFJxBJYxR7\n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\nkQAKgEgABfj/eVKPyALu1jYAAAAASUVORK5CYII=", - "text/plain": [ - "plot without title" - ] - }, - "metadata": {}, - "output_type": "display_data" + "cell_type": "code", + "execution_count": 4, + "metadata": { + "trusted": true + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAMAAADKOT/pAAACWFBMVEUAAAABAQEDAwMEBAQF\nBQUGBgYHBwcJCQkKCgoNDQ0PDw8QEBARERESEhITExMVFRUWFhYXFxcZGRkbGxscHBwfHx8g\nICAhISEmJiYnJycrKystLS0yMjIzMzM1NTU4ODg5OTk7OztBQUFCQkJDQ0NFRUVGRkZKSkpM\nTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5f\nX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZoaGhpaWlqampra2tsbGxubm5vb29ycnJzc3N1dXV2\ndnZ3d3d4eHh5eXl6enp8fHx/f3+AgICCgoKDg4OFhYWGhoaHh4eJiYmLi4uMjIyNjY2Ojo6P\nj4+QkJCRkZGTk5OUlJSVlZWWlpaXl5eZmZmampqbm5ucnJydnZ2enp6fn5+goKChoaGioqKj\no6OkpKSmpqaoqKipqamqqqqrq6usrKytra2urq6wsLCxsbGysrKzs7O0tLS1tbW2tra3t7e4\nuLi5ubm6urq7u7u8vLy9vb2+vr6/v7/AwMDBwcHCwsLDw8PExMTFxcXGxsbHx8fIyMjJycnK\nysrLy8vMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT09PU1NTW1tbX19fY2NjZ2dna2trb29vc3Nzd\n3d3f39/g4ODh4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w\n8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7////IoxbwAAAA\nCXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nO2d/Z9d1XWfx05a9z1t+pqmJG1St71CqMIm\nOCpFsWlLmpS4hZi3pA2ucZMUYhqDIXZK7cSExGlMS0qoIsuWQFVFIlwsZAkhgTRIM3PPv9U7\ns7UuXDxaa9/v2Wdmn3Oe54erl883ey99135gBMhZaQCgNSu7PQDAEEAkgAIgEkABEAmgAIgE\nUABEAigAIgEUAJEACtBCpDffWGDjyhud8Nblbs69vHGxm4Ovnu/mXAq+Rk0FlxDpje++l7PN\n1e92wsXL3Zx7uXmzm4PXz3ZyLAUbNRWMSOPYcw4UnEAkjVHsOQcKTiCSxij2nAMFJxBJYxR7\n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\nkQAKgEgABfj/eVKPyALu1jYAAAAASUVORK5CYII=", + "text/plain": "plot without title" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": "ggplot(df, aes(x=x, y=data)) + geom_bar(stat='identity')" } - ], - "source": [ - "ggplot(df, aes(x=x, y=data)) + geom_bar(stat='identity')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "R", - "language": "R", - "name": "ir" + ], + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "3.4.2" + }, + "output": "html_notebook" }, - "language_info": { - "codemirror_mode": "r", - "file_extension": ".r", - "mimetype": "text/x-r-source", - "name": "R", - "pygments_lexer": "r", - "version": "3.4.2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/examples/ex7.rmarkdown.Rmd b/examples/ex7.rmarkdown.Rmd new file mode 100644 index 0000000..ce04613 --- /dev/null +++ b/examples/ex7.rmarkdown.Rmd @@ -0,0 +1,39 @@ +--- +kernelspec: + display_name: R + language: R + name: ir +language_info: + codemirror_mode: r + file_extension: .r + mimetype: text/x-r-source + name: R + pygments_lexer: r + version: 3.4.2 +output: html_notebook +--- + +## R notebook with R kernel + +Some literal R code which should not be evaluated +```r +stop("not evaluated") +``` + +```{R, trusted=TRUE} +library(tidyverse) +``` + +```{R, trusted=TRUE} +data = 1:20 * 2 +data +``` + +```{R, trusted=TRUE} +df = tibble(x=1:20, data=data) +df +``` + +```{R, trusted=TRUE} +ggplot(df, aes(x=x, y=data)) + geom_bar(stat='identity') +``` diff --git a/examples/ex7.rmarkdown.nb.html b/examples/ex7.rmarkdown.nb.html new file mode 100644 index 0000000..c0b443a --- /dev/null +++ b/examples/ex7.rmarkdown.nb.html @@ -0,0 +1,509 @@ + + + + + + + + + + + + + +IPYMD Notebook + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +

R notebook with R kernel

+

Some literal R code which should not be evaluated

+
stop("not evaluated")
+ + + +
library(tidyverse)
+ + +
Loading tidyverse: ggplot2
+Loading tidyverse: tibble
+Loading tidyverse: tidyr
+Loading tidyverse: readr
+Loading tidyverse: purrr
+Loading tidyverse: dplyr
+Conflicts with tidy packages ---------------------------------------------------
+filter(): dplyr, stats
+lag():    dplyr, stats
+ + + + +
data = 1:20 * 2
+data
+ + +
    +
  1. 2
  2. +
  3. 4
  4. +
  5. 6
  6. +
  7. 8
  8. +
  9. 10
  10. +
  11. 12
  12. +
  13. 14
  14. +
  15. 16
  16. +
  17. 18
  18. +
  19. 20
  20. +
  21. 22
  22. +
  23. 24
  24. +
  25. 26
  26. +
  27. 28
  28. +
  29. 30
  30. +
  31. 32
  32. +
  33. 34
  34. +
  35. 36
  36. +
  37. 38
  38. +
  39. 40
  40. +
+ + + + +
df = tibble(x=1:20, data=data)
+df
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
xdata
1 2
2 4
3 6
4 8
510
612
714
816
918
1020
1122
1224
1326
1428
1530
1632
1734
1836
1938
2040
+ + + + +
ggplot(df, aes(x=x, y=data)) + geom_bar(stat='identity')
+ + +

+ + + +
LS0tCmtlcm5lbHNwZWM6CiAgZGlzcGxheV9uYW1lOiBSCiAgbGFuZ3VhZ2U6IFIKICBuYW1lOiBpcgpsYW5ndWFnZV9pbmZvOgogIGNvZGVtaXJyb3JfbW9kZTogcgogIGZpbGVfZXh0ZW5zaW9uOiAucgogIG1pbWV0eXBlOiB0ZXh0L3gtci1zb3VyY2UKICBuYW1lOiBSCiAgcHlnbWVudHNfbGV4ZXI6IHIKICB2ZXJzaW9uOiAzLjQuMgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBSIG5vdGVib29rIHdpdGggUiBrZXJuZWwKClNvbWUgbGl0ZXJhbCBSIGNvZGUgd2hpY2ggc2hvdWxkIG5vdCBiZSBldmFsdWF0ZWQKYGBgcgpzdG9wKCJub3QgZXZhbHVhdGVkIikKYGBgCgpgYGB7UiwgdHJ1c3RlZD1UUlVFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpgYGB7UiwgdHJ1c3RlZD1UUlVFfQpkYXRhID0gMToyMCAqIDIKZGF0YQpgYGAKCmBgYHtSLCB0cnVzdGVkPVRSVUV9CmRmID0gdGliYmxlKHg9MToyMCwgZGF0YT1kYXRhKQpkZgpgYGAKCmBgYHtSLCB0cnVzdGVkPVRSVUV9CmdncGxvdChkZiwgYWVzKHg9eCwgeT1kYXRhKSkgKyBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpCmBgYAo=
+ + + +
+ + + + + + + + \ No newline at end of file diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 94d2091..70f44e6 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -403,12 +403,16 @@ def _create_output_tag(self, output): as base64 string. """ output = _stream_output_to_result(output) + assert output['output_type'] in ['execute_result', 'display_data', 'error'] if output['output_type'] == 'error': yield from self._create_output_tag_error(output) else: + if len(output.get('data', [])) == 0: + # ignore emtpy outputs + return # look for these mimetypes in the given order. # the first one matching will be visible in html. display_types = OrderedDict() @@ -502,7 +506,7 @@ def _create_tag(tag_name, tag_content, tag_meta=None): """ meta_b64 = "" if tag_meta is None or tag_meta == {} else _b64_encode( - json.dumps(tag_meta)) + json.dumps(tag_meta, sort_keys=True)) return "\n" \ "{contents}" \ "".format(tag=tag_name, b64=meta_b64, diff --git a/ipymd/formats/tests/test_rmarkdown.py b/ipymd/formats/tests/test_rmarkdown.py index 12ea837..9e8118a 100644 --- a/ipymd/formats/tests/test_rmarkdown.py +++ b/ipymd/formats/tests/test_rmarkdown.py @@ -515,3 +515,19 @@ def test_ex6_rmarkdown_rmarkdown(): def test_ex6_notebook_notebook(): _test_notebook_notebook('ex6') + + +def test_ex7_reader(): + _test_rmarkdown_reader('ex7') + + +def test_ex7_writer(): + _test_rmarkdown_writer('ex7') + + +def test_ex7_rmarkdown_rmarkdown(): + _test_rmarkdown_rmarkdown('ex7') + + +def test_ex7_notebook_notebook(): + _test_notebook_notebook('ex7') From efb945b64f57fe193f05912c69c80d2267d67fc0 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 18 Nov 2017 19:26:29 +0100 Subject: [PATCH 28/33] Fix linting error. --- ipymd/formats/tests/test_markdown.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipymd/formats/tests/test_markdown.py b/ipymd/formats/tests/test_markdown.py index 3bfccb4..66d6691 100644 --- a/ipymd/formats/tests/test_markdown.py +++ b/ipymd/formats/tests/test_markdown.py @@ -91,7 +91,8 @@ def test_decorator(): def test_md_notebook_metadata(): - """Test that reading and writing complex notebook metadata results in the identity. """ + """Test that reading and writing complex notebook metadata + results in the identity. """ mock_metadata = '\n'.join(('---', 'author: John Doe', 'date: 2017-05-07', From 9a6a50e42f5173a4de32f83d25d8196aaaed7bc3 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 18 Nov 2017 19:29:28 +0100 Subject: [PATCH 29/33] Documentation of Makefile. --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 87152d3..45c18aa 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,11 @@ help: @echo "clean - remove all build, test, coverage and Python artifacts" @echo "clean-build - remove build artifacts" @echo "clean-pyc - remove Python file artifacts" + @echo "clean-venv - remove Python virtual environment" + @echo "python - create Python virtual environment and install all required packages" @echo "lint - check style with flake8" @echo "test - run tests quickly with the default Python" + @echo "jupyter - run jupyter notebook in a virtual environment with ipymd" clean: clean-build clean-pyc clean-venv @@ -26,7 +29,7 @@ clean-venv: rm -rf venv/ lint: | $(PYTHON) - $(VENV)/flake8 ipymd setup.py --exclude=ipymd/ext/six.py,ipymd/core/contents_manager.py --ignore=E226,E265,F401,F403,F811 + $(VENV)/flake8 ipymd setup.py --exclude=ipymd/ext/six.py,ipymd/core/contents_manager.py,ipymd/formats/tests/test_rmarkdown.py --ignore=E226,E265,F401,F403,F811 test: lint | $(PYTHON) $(PYTHON) setup.py test From cb5bb764be8c520ca0b6aadbfe4244ce06795084 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 18 Nov 2017 22:48:35 +0100 Subject: [PATCH 30/33] update readme. --- README.md | 226 ++++++++++++++++++++++++++---------------------------- 1 file changed, 107 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index f131dc9..617b453 100644 --- a/README.md +++ b/README.md @@ -1,151 +1,142 @@ [![Build Status](https://travis-ci.org/rossant/ipymd.svg?branch=travis)](https://travis-ci.org/rossant/ipymd) [![Coverage Status](https://coveralls.io/repos/rossant/ipymd/badge.svg)](https://coveralls.io/r/rossant/ipymd) -# Replace .ipynb with .md in the IPython Notebook -** the goal of this for is to implement the [R Notebook](http://rmarkdown.rstudio.com/r_notebook_format.html) format as a file format for jupyter ** - -This format stores the output in a separate `.nb.html` file, while the code chunks and markdown cells go into a `.Rmd` file. -This has the advantage over pure markdown, that the output can be stored (including images) and the advantage over `.ipynb` that it works well with git and is editable in a text editor. - - ---------- - - -The goal of ipymd is to replace `.ipynb` notebook files like: - -```json -{ - "cells": [ - { - "cell_type": "markdown", - "source": [ - "Here is some Python code:" - ] - }, - { - "cell_type": "code", - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Hello world!\n" - ] - } - ], - "source": [ - "print(\"Hello world!\")" - ] - } - ... - ] -} -``` - -with: +# Store Jupyter notebooks in markdown format. +This package provides an alternative content manager for jupyter. +It allows to store notebooks in text-based formats, replacing the native json-based `.ipynb`. - Here is some Python code: +This combines the advantages of a simple, text-based format (vi and git-friendly) with jupyter's powerful UI for interactively editing code and text. - ```python - >>> print("Hello world!") - Hello world! - ``` -The JSON `.ipynb` are removed from the equation, and the conversion happens on the fly. The IPython Notebook becomes an interactive Markdown text editor! +## Overview of formats +Ipymd currently supports the following formats: -A drawback is that you lose prompt numbers and images (for now). +| Format | Extension | vi | git | images | +| ------------ | ------------------ | -- | --- | ------ | +| [notebook](#ipython-notebook-ipynb) | `.ipynb` | | | ✔ | +| [rmarkdown](#rmarkdown-rmd--rnotebook-nbhtml) | `.Rmd`, `.nb.html` | ✔ | ✔ | ✔ | +| [markdown]() | `.md` | ✔ | (✔) | | +| [atlas](#oreilly-atlas-md) | `.md` | ✔ | (✔) | | +| [opendocument](#opendocument-odt) | `.odt` | | | | +| [python](#python-py) | `.py` | ✔ | (✔) | | -This is useful when you write technical documents, blog posts, books, etc. +✔ works; (✔) works with limitations -![image](https://cloud.githubusercontent.com/assets/1942359/5570181/f656a484-8f7d-11e4-8ec2-558d022b13d3.png) -## Installation +## Usage +Ipymd hooks into jupyter, enabling to open the files directly in jupyter notebook. -1. Install ipymd: +Alternatively, you can use ipymd to convert between the formats from command line: - To install the latest release version: - - ```shell - pip install ipymd - ``` - - Alternatively, to install the development version: +``` +usage: ipymd [-h] --from FROM_ --to TO [--output OUTPUT] + [--extension EXTENSION] [--overwrite] + files_or_dirs [files_or_dirs ...] + +Convert files across formats supported by ipymd. + +positional arguments: + files_or_dirs list of files or directories to convert + +optional arguments: + -h, --help show this help message and exit + --from FROM_ one of atlas, markdown, notebook, opendocument, + python, rmarkdown + --to TO one of atlas, markdown, notebook, opendocument, + python, rmarkdown + --output OUTPUT output folder + --extension EXTENSION + output file extension + --overwrite overwrite target file if it exists (false by default) +``` - ```shell - pip install git+https://github.com/rossant/ipymd - ``` -2. **Optional:** - To interact with `.ipynb` files: +## Installation +There are two possibilities to use ipymd: +1. **Within a virtual environment, for testing and developing** ```shell - pip install jupyter ipython + git clone https://github.com/rossant/ipymd + make jupyter ``` + will setup a virtual environment and run a `jupyter notebook` instance with ipymd activated. - To interact with `.odt` files: - - ```shell - pip install git+https://github.com/eea/odfpy + You can choose the format by editing `.jupyter/jupyter_notebook_config.py`: + ```python + c.IPymdContentsManager.format = 'rmarkdown' # choose the format here ``` -3. Open your `jupyter_notebook_config.py`. Here's how to find it: +2. **Integrated into your local jupyter installation** + * Install ipymd + ```shell + pip install ipymd + ``` - ``` - jupyter notebook --generate-config # generate a default config file - jupyter --config-dir # find out the path to the config file - ``` + * Open your `jupyter_notebook_config.py`. Here's how to find it: + ``` + jupyter notebook --generate-config # generate a default config file + jupyter --config-dir # find out the path to the config file + ``` -4. Add the following in `jupyter_notebook_config.py`: + * Add the following in `jupyter_notebook_config.py`: + ```python + c.NotebookApp.contents_manager_class = 'ipymd.IPymdContentsManager' + c.IPymdContentsManager.format = 'rmarkdown' # choose the format here + ``` - ```python - c.NotebookApp.contents_manager_class = 'ipymd.IPymdContentsManager' - ``` + * (re)start jupyter -5. Now, you can open `.md` files in the Notebook. +**Optional:** +To interact with `.odt` files: -## Why? +```shell +pip install git+https://github.com/eea/odfpy +``` -### IPython Notebook +## Caveats -Pros: +**WARNING**: use this library at your own risks, backup your data, and version-control your notebooks and Markdown files! -* Excellent UI for executing code interactively *and* writing text +* Renaming doesn't work yet (issue #4) +* New notebook doesn't work yet (issue #5) +* Only nbformat v4 is supported currently (IPython 3.0) -Cons: -* `.ipynb` not git-friendly -* Cannot easily edit in a text editor -* Cannot easily edit on GitHub's web interface +## Caveats -### Markdown +**WARNING**: use this library at your own risks, backup your data, and version-control your notebooks and Markdown files! -Pros: +* Renaming doesn't work yet (issue #4) +* New notebook doesn't work yet (issue #5) +* Only nbformat v4 is supported currently (IPython 3.0) -* Simple ASCII/Unicode format to write code and text -* Can easily edit in a text editor -* Can easily edit on GitHub's web interface -* Git-friendly -Cons: -* No UI to execute code interactively +## Formats +### IPython notebook (`.ipynb`) +Jupyter's default notebook format. It stores cells as json-objects. +The main downsides of this format are +* not git-friendly +* cannot easily edit in a text editor +* cannot easily edit on GitHub's web interface +[Format documentation](http://nbformat.readthedocs.io/en/latest/) -### ipymd +### RMarkdown (`.Rmd`) / RNotebook (`.nb.html`) +RMarkdown is propagated by rstudio and widely adopted within the R community. +Unlike the name suggests, it can very well be used with python. -All pros of IPython Notebook and Markdown, no cons! +The clue about this format is, that it strictly separates source code from output. +This makes it the format of choice when working with version control. +While the source code is stored as markdown in a `.Rmd` file, the results go into a +`.nb.html` file which can also be viewed in a browser. -## How it works +[Format documentation](http://rmarkdown.rstudio.com/r_notebooks.html) -* Write in Markdown in `document.md` - * Either in a text editor (convenient when working on text) - * Or in the Notebook (convenient when writing code examples) -* Markdown cells, code cells and (optionally) notebook metadata are saved in - the file -* Collaborators can work on the Markdown document using GitHub's web interface. +### Markdown (`.md`) * By convention, a **notebook code cell** is equivalent to a **Markdown code block with explicit `python` syntax highlighting**: ``` @@ -202,26 +193,23 @@ All pros of IPython Notebook and Markdown, no cons! * Text output and standard output are combined into a single text output (stdout lines first, output lines last) -## Caveats -**WARNING**: use this library at your own risks, backup your data, and version-control your notebooks and Markdown files! +### O'Reilly Atlas (`.md`) +* `.md` with special HTML tags for code and mathematical equations +[Format documentation](http://odewahn.github.io/publishing-workflows-for-jupyter/#1) ( -* Renaming doesn't work yet (issue #4) -* New notebook doesn't work yet (issue #5) -* Only nbformat v4 is supported currently (IPython 3.0) +### Python (`.py`) +* code cells are delimited by double line breaks. +* Markdown cells = Python comments. +* [TODO: this doesn't work well, see #28 and #31] +### Opendocument (`.odt`). +* You need to install the [development version of odfpy](https://github.com/eea/odfpy/). -## Formats -ipymd uses a modular architecture that lets you define new formats. The following formats are currently implemented, and can be selected by modifying `~/.ipython/profile_/ipython_notebook_config.py`: -* IPython notebook (`.ipynb`) -* Markdown (`.md`) - * `c.IPymdContentsManager.format = 'markdown'` -* [O'Reilly Atlas](http://odewahn.github.io/publishing-workflows-for-jupyter/#1) (`.md` with special HTML tags for code and mathematical equations) - * `c.IPymdContentsManager.format = 'atlas'` -* Python (`.py`): code cells are delimited by double line breaks. Markdown cells = Python comments. [TODO: this doesn't work well, see #28 and #31] -* Opendocument (`.odt`). You need to install the [development version of odfpy](https://github.com/eea/odfpy/). +## Implementing your own format +ipymd uses a modular architecture that lets you define new formats. The following formats are currently implemented, and can be selected by modifying `~/.ipython/profile_/ipython_notebook_config.py`: You can convert from any supported format to any supported format. This works by converting to an intermediate format that is basically a list of notebook cells. From 80fbee4c6529d5ad4778ada1b6efc61123e45f5a Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sat, 18 Nov 2017 23:03:14 +0100 Subject: [PATCH 31/33] Fix README --- README.md | 10 ---------- ipymd/formats/rmarkdown.py | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index 617b453..9505531 100644 --- a/README.md +++ b/README.md @@ -104,16 +104,6 @@ pip install git+https://github.com/eea/odfpy -## Caveats - -**WARNING**: use this library at your own risks, backup your data, and version-control your notebooks and Markdown files! - -* Renaming doesn't work yet (issue #4) -* New notebook doesn't work yet (issue #5) -* Only nbformat v4 is supported currently (IPython 3.0) - - - ## Formats ### IPython notebook (`.ipynb`) Jupyter's default notebook format. It stores cells as json-objects. diff --git a/ipymd/formats/rmarkdown.py b/ipymd/formats/rmarkdown.py index 70f44e6..e53a2ba 100644 --- a/ipymd/formats/rmarkdown.py +++ b/ipymd/formats/rmarkdown.py @@ -282,7 +282,7 @@ def __del__(self): class RmdWriter(BaseMarkdownWriter): - """Default .Rmd writer.""" + """.Rmd writer""" def __init__(self, rmarkdown_writer): super(RmdWriter, self).__init__() From 8e68d99f57988be92cd8ca283205d14db763d240 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sun, 19 Nov 2017 12:02:29 +0100 Subject: [PATCH 32/33] Update README.md --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9505531..966c451 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,12 @@ Ipymd hooks into jupyter, enabling to open the files directly in jupyter noteboo Alternatively, you can use ipymd to convert between the formats from command line: ``` -usage: ipymd [-h] --from FROM_ --to TO [--output OUTPUT] - [--extension EXTENSION] [--overwrite] - files_or_dirs [files_or_dirs ...] - -Convert files across formats supported by ipymd. - -positional arguments: - files_or_dirs list of files or directories to convert +ipymd my_notebook.ipynb --from notebook --to markdown +``` -optional arguments: +Additional options: +``` -h, --help show this help message and exit - --from FROM_ one of atlas, markdown, notebook, opendocument, - python, rmarkdown - --to TO one of atlas, markdown, notebook, opendocument, - python, rmarkdown --output OUTPUT output folder --extension EXTENSION output file extension @@ -126,6 +117,66 @@ While the source code is stored as markdown in a `.Rmd` file, the results go int [Format documentation](http://rmarkdown.rstudio.com/r_notebooks.html) + +#### Known issues +See [grst/ipymd/issues](https://github.com/grst/ipymd/issues) for issues related to rmarkdown. Major issues: +* HTML formatting can be improved +* Some output is not compatible with rstudio + +#### Implementation of `.Rmd` format +* markdown cells are saved as plain markdown +* code cells are saved as code chunks, separated by a newline + ~~~ + ```{python, some="meta", data=True} + print("Hello World!") + ``` + ~~~ + Note the curly braced `{}` which distinguish an executed code chunk from + a code chunk within markdown. +* metadata is saved as chunk options. + * Both python and R literals are supported (`NULL`, `None`, `TRUE`, `True`, `FALSE`, `False`), + but always saved as R literals to maintain compatibility with rstudio. + * Both single and double quoted strings are supported. + * We try to parse unquoted options as literal, then as integer, then as float. If all three fail a `TypeError` is raised. + +#### Implementation of `nb.html` format. +* This format stores the outputs of the notebook in a way that + * the outputs can be read from jupyter + * the entire notebook can be viewed from a browser +* a html templated is used, which is filled using `jinja2`. +* markdown cells are saved within `...` tags +* code cells are saved within `chunk` tags: + ``` + + +
...
+ + + ... + + + + + + ``` +* tags cannot be nested +* a `chunk` may hold an arbitrary number of `outputs` +* tags hold data as base64 encoded json dictionaries as follows: + * rnb-source-begin: + ~~~ + {'data': '```python\n chunk as markdown```'} + ~~~ + * rnb-output-begin/rnb-plot-begin + ~~~ + {'data': '', # fallback for rstudio + 'ipymd.data': {'text/plain': ..., # output['data'] from jupyter nbformat + 'image/png': ..., + ... }, + 'ipymd.metadata': {}, # output['metadata'] from jupyter nbformat + 'ipymd.output_type': 'display_data'} # output['output_type'] from jupyter nbformat + ~~~ + + ### Markdown (`.md`) * By convention, a **notebook code cell** is equivalent to a **Markdown code block with explicit `python` syntax highlighting**: From 320118350e1d1c037854454eba359035bb33f818 Mon Sep 17 00:00:00 2001 From: Gregor Sturm Date: Sun, 19 Nov 2017 13:07:48 +0100 Subject: [PATCH 33/33] Add pandoc to venv and fix pandoc version problems in tests. --- .gitignore | 1 + Makefile | 1 + examples/ex5.rmarkdown.nb.html | 2 +- examples/ex6.rmarkdown.nb.html | 4 ++-- examples/ex7.rmarkdown.nb.html | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 87c019e..f0d1478 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ dist/ pytest.ini venv sandbox +pandoc-* # internal script for development convert_testcases.sh diff --git a/Makefile b/Makefile index 45c18aa..53c7924 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,7 @@ $(PIP): | venv venv/.installed: requirements-dev.txt | venv $(PIP) install -Ur requirements-dev.txt $(PIP) install -e . + $(PYTHON) -c 'import pypandoc; pypandoc.download_pandoc(version="1.19.1")' echo "pip install successful" > $@ $(PYTHON): | $(PIP) venv/.installed diff --git a/examples/ex5.rmarkdown.nb.html b/examples/ex5.rmarkdown.nb.html index 6a96dd7..273fec5 100644 --- a/examples/ex5.rmarkdown.nb.html +++ b/examples/ex5.rmarkdown.nb.html @@ -194,7 +194,7 @@

Header

JavaScript code:

-
console.log("Hello world!");
+
console.log("Hello world!");

test

diff --git a/examples/ex6.rmarkdown.nb.html b/examples/ex6.rmarkdown.nb.html index 46c1e6a..84ef3cd 100644 --- a/examples/ex6.rmarkdown.nb.html +++ b/examples/ex6.rmarkdown.nb.html @@ -174,7 +174,7 @@

IPYMD Notebook

Python notebook tests

Some literal python which is not evaluated:

-
print("Hello World!")
+
print("Hello World!")

Advanved Markdown

Foo bar kk

$\sum_{i=1}^n 2^i$

@@ -818,4 +818,4 @@

An Error

- \ No newline at end of file + diff --git a/examples/ex7.rmarkdown.nb.html b/examples/ex7.rmarkdown.nb.html index c0b443a..23e45cd 100644 --- a/examples/ex7.rmarkdown.nb.html +++ b/examples/ex7.rmarkdown.nb.html @@ -174,7 +174,7 @@

IPYMD Notebook

R notebook with R kernel

Some literal R code which should not be evaluated

-
stop("not evaluated")
+
stop("not evaluated")
@@ -506,4 +506,4 @@

R notebook with R kernel

- \ No newline at end of file +