Repository: mwouts/jupytext
Pull request #351 (2019-10-16 20:25:52)

Add support for Rust/Evxcr
I tried to follow the listed for "Add support for another language". I do have some questions about the last point though.

Evcxr has special commands, which are not valid Rust in any way. In contrast to Python magic cells they start with `:` instead of `%`, so they are not covered by the current logic for line magic.
From the code it looked like changing the variables `_MAGIC_RE`/`_MAGIC_FORCE_ESC_RE` might be the way to go here, but since I do not see any special casing there yet, I first wanted to know if that would be correct.

Here is a list of the supported magics in Evcxr. 
```
:vars             List bound variables and their types
:opt [level]      Toggle/set optimization level
:explain          Print explanation of last error
:clear            Clear all state, keeping compilation cache
:dep              Add dependency. e.g. :dep regex = "1.0"
:version          Print Evcxr version
:preserve_vars_on_panic [0|1]  Try to keep vars on panic

Mostly for development / debugging purposes:
:last_compile_dir Print the directory in which we last compiled
:timing           Toggle printing of how long evaluations take
:last_error_json  Print the last compilation error as JSON (for debugging)
:time_passes      Toggle printing of rustc pass times (requires nightly)
:internal_debug   Toggle various internal debugging code
```

Rust identifiers can also contain `:` at the start, for example `::std::mem::drop`.
The difference is that Evcxr commands have exactly one and in identifiers it is always as `::`.

The sample notebook stems from the [Evcxr](https://github.com/google/evcxr) project, which provides a Rust kernel.
https://github.com/google/evcxr/blob/942e166965e47073a592864440acd59cb8f4dfe1/evcxr_jupyter/samples/evcxr_jupyter_tour.ipynb


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 76808bbc..60556ed1 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -12,6 +12,7 @@ Release History
 - Markdown and raw cells can use multiline comments in the ``py:percent`` format (#305)
 - ``jupytext notebook.py --to ipynb`` updates the timestamp of ``notebook.py`` so that the paired notebook still works in Jupyter (#335, #254)
 - The Jupyter Notebook extension for Jupytext is compatible with Jupyter Notebook 6.0 (#346)
+- Added support for Rust/Evxcr, by Jonas Bushart (#351)
 
 **BugFixes**
 
diff --git a/jupytext/languages.py b/jupytext/languages.py
index 7668e898..06be0c60 100644
--- a/jupytext/languages.py
+++ b/jupytext/languages.py
@@ -21,7 +21,8 @@ _SCRIPT_EXTENSIONS = {'.py': {'language': 'python', 'comment': '#'},
                       '.pro': {'language': 'idl', 'comment': ';'},
                       '.js': {'language': 'javascript', 'comment': '//'},
                       '.ts': {'language': 'typescript', 'comment': '//'},
-                      '.scala': {'language': 'scala', 'comment': '//'}}
+                      '.scala': {'language': 'scala', 'comment': '//'},
+                      '.rs': {'language': 'rust', 'comment': '//'}}
 
 _COMMENT_CHARS = [_SCRIPT_EXTENSIONS[ext]['comment'] for ext in _SCRIPT_EXTENSIONS if
                   _SCRIPT_EXTENSIONS[ext]['comment'] != '#']
diff --git a/jupytext/magics.py b/jupytext/magics.py
index 9dbfc5d0..cf38d4b3 100644
--- a/jupytext/magics.py
+++ b/jupytext/magics.py
@@ -15,6 +15,11 @@ _MAGIC_NOT_ESC_RE = {_SCRIPT_EXTENSIONS[ext]['language']: re.compile(
         _SCRIPT_EXTENSIONS[ext]['comment'])) for ext in _SCRIPT_EXTENSIONS}
 _LINE_CONTINUATION_RE = re.compile(r'.*\\\s*$')
 
+# Rust magics start with single ':' #351
+_MAGIC_RE['rust'] = re.compile(r"^(// |//)*:[a-zA-Z]")
+_MAGIC_FORCE_ESC_RE['rust'] = re.compile(r"^(// |//)*:[a-zA-Z](.*)//\s*escape")
+_MAGIC_FORCE_ESC_RE['rust'] = re.compile(r"^(// |//)*:[a-zA-Z](.*)//\s*noescape")
+
 # Commands starting with a question or exclamation mark have to be escaped
 _PYTHON_HELP_OR_BASH_CMD = re.compile(r"^(# |#)*(\?|!)\s*[A-Za-z]")
 
@@ -25,16 +30,18 @@ _PYTHON_MAGIC_CMD = re.compile(r"^(# |#)*({})($|\s$|\s[^=,])".format('|'.join(
     # windows
     ['copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', 'rmdir'])))
 
+_SCRIPT_LANGUAGES = [_SCRIPT_EXTENSIONS[ext]['language'] for ext in _SCRIPT_EXTENSIONS]
+
 
 def is_magic(line, language, global_escape_flag=True):
     """Is the current line a (possibly escaped) Jupyter magic, and should it be commented?"""
-    if language in ['octave', 'matlab']:
+    if language in ['octave', 'matlab'] or language not in _SCRIPT_LANGUAGES:
         return False
-    if _MAGIC_FORCE_ESC_RE.get(language, _MAGIC_FORCE_ESC_RE['python']).match(line):
+    if _MAGIC_FORCE_ESC_RE[language].match(line):
         return True
-    if not global_escape_flag or _MAGIC_NOT_ESC_RE.get(language, _MAGIC_NOT_ESC_RE['python']).match(line):
+    if not global_escape_flag or _MAGIC_NOT_ESC_RE[language].match(line):
         return False
-    if _MAGIC_RE.get(language, _MAGIC_RE['python']).match(line):
+    if _MAGIC_RE[language].match(line):
         return True
     if language != 'python':
         return False


Test patch:
diff --git a/tests/notebooks/ipynb_rust/evcxr_jupyter_tour.ipynb b/tests/notebooks/ipynb_rust/evcxr_jupyter_tour.ipynb
new file mode 100644
index 00000000..d144a9ea
--- /dev/null
+++ b/tests/notebooks/ipynb_rust/evcxr_jupyter_tour.ipynb
@@ -0,0 +1,513 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Tour of the EvCxR Jupyter Kernel\n",
+    "For those not already familiar with Jupyter notebook, it lets you write code into \"cells\" like the box below. Cells can alternatively contain markdown, like this text here. Each code cell is compiled and executed separately, but variables, defined functions etc persist between cells.\n",
+    "\n",
+    "## Printing to outputs and evaluating expressions\n",
+    "Lets print something to stdout and stderr then return a final expression to see how that's presented. Note that stdout and stderr are separate streams, so may not appear in the same order is their respective print statements."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "Hello error\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Hello world\n"
+     ]
+    },
+    {
+     "data": {
+      "text/plain": [
+       "\"Hello world\""
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "println!(\"Hello world\");\n",
+    "eprintln!(\"Hello error\");\n",
+    "format!(\"Hello {}\", \"world\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Assigning and making use of variables\n",
+    "We define a variable `message`, then in the subsequent cell, modify the string and finally print it out. We could also do all this in the one cell if we wanted."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "let mut message = \"Hello \".to_owned();"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "message.push_str(\"world!\");"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\"Hello world!\""
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "message"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Defining and redefining functions\n",
+    "Next we'll define a function"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pub fn fib(x: i32) -> i32 {\n",
+    "    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]"
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "(1..13).map(fib).collect::<Vec<i32>>()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Hmm, that doesn't look right. Lets redefine the function. In practice, we'd go back and edit the function above and reevalute it, but here, lets redefine it in a separate cell."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pub fn fib(x: i32) -> i32 {\n",
+    "    if x <= 2 {2} else {fib(x - 2) + fib(x - 1)}\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[2, 2, 4, 6, 10, 16, 26, 42, 68, 110, 178, 288]"
+      ]
+     },
+     "execution_count": 9,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "let values = (1..13).map(fib).collect::<Vec<i32>>();\n",
+    "values"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Spawning a separate thread and communicating with it\n",
+    "We can spawn a thread to do stuff in the background, then continue executing code in other cells."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "use std::sync::{Mutex, Arc};\n",
+    "let counter = Arc::new(Mutex::new(0i32));\n",
+    "std::thread::spawn({\n",
+    "    let counter = Arc::clone(&counter);\n",
+    "    move || {\n",
+    "        for i in 1..300 {\n",
+    "            *counter.lock().unwrap() += 1;\n",
+    "            std::thread::sleep(std::time::Duration::from_millis(100));\n",
+    "        }\n",
+    "}});"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "17"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "*counter.lock().unwrap()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "29"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "*counter.lock().unwrap()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Loading external crates\n",
+    "We can load external crates. This one takes a while to compile, but once it's compiled, subsequent cells shouldn't need to recompile it, so it should be much quicker."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "\"AQIDBA==\""
+      ]
+     },
+     "execution_count": 14,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    ":dep base64 = \"0.10.1\"\n",
+    "base64::encode(&vec![1, 2, 3, 4])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Customizing how types are displayed\n",
+    "We can also customize how our types are displayed, including presenting them as HTML. Here's an example where we define a custom display function for a type `Matrix`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "use std::fmt::Debug;\n",
+    "pub struct Matrix<T> {pub values: Vec<T>, pub row_size: usize}\n",
+    "impl<T: Debug> Matrix<T> {\n",
+    "    pub fn evcxr_display(&self) {\n",
+    "        let mut html = String::new();\n",
+    "        html.push_str(\"<table>\");\n",
+    "        for r in 0..(self.values.len() / self.row_size) {\n",
+    "            html.push_str(\"<tr>\");\n",
+    "            for c in 0..self.row_size {\n",
+    "                html.push_str(\"<td>\");\n",
+    "                html.push_str(&format!(\"{:?}\", self.values[r * self.row_size + c]));\n",
+    "                html.push_str(\"</td>\");\n",
+    "            }\n",
+    "            html.push_str(\"</tr>\");            \n",
+    "        }\n",
+    "        html.push_str(\"</table>\");\n",
+    "        println!(\"EVCXR_BEGIN_CONTENT text/html\\n{}\\nEVCXR_END_CONTENT\", html);\n",
+    "    }\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<table><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>4</td><td>5</td><td>6</td></tr><tr><td>7</td><td>8</td><td>9</td></tr></table>"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "let m = Matrix {values: vec![1,2,3,4,5,6,7,8,9], row_size: 3};\n",
+    "m"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We can also return images, we just need to base64 encode them. First, we set up code for displaying RGB and grayscale images."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "extern crate image;\n",
+    "extern crate base64;\n",
+    "pub trait EvcxrResult {fn evcxr_display(&self);}\n",
+    "impl EvcxrResult for image::RgbImage {\n",
+    "    fn evcxr_display(&self) {\n",
+    "        let mut buffer = Vec::new();\n",
+    "        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),\n",
+    "            image::ColorType::RGB(8)).unwrap();\n",
+    "        let img = base64::encode(&buffer);\n",
+    "        println!(\"EVCXR_BEGIN_CONTENT image/png\\n{}\\nEVCXR_END_CONTENT\", img);        \n",
+    "    }\n",
+    "}\n",
+    "impl EvcxrResult for image::GrayImage {\n",
+    "    fn evcxr_display(&self) {\n",
+    "        let mut buffer = Vec::new();\n",
+    "        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),\n",
+    "            image::ColorType::Gray(8)).unwrap();\n",
+    "        let img = base64::encode(&buffer);\n",
+    "        println!(\"EVCXR_BEGIN_CONTENT image/png\\n{}\\nEVCXR_END_CONTENT\", img);        \n",
+    "    }\n",
+    "}"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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"
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "image::ImageBuffer::from_fn(256, 256, |x, y| {\n",
+    "    if (x as i32 - y as i32).abs() < 3 {\n",
+    "        image::Rgb([0, 0, 255])\n",
+    "    } else {\n",
+    "        image::Rgb([0, 0, 0])\n",
+    "    }\n",
+    "})"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Display of compilation errors\n",
+    "Here's how compilation errors are presented. Here we forgot an & and passed a String instead of an &str."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "Error",
+     "evalue": "mismatched types",
+     "output_type": "error",
+     "traceback": [
+      "s.push_str(format!(\"foo {}\", 42));",
+      "\u001b[91m           ^^^^^^^^^^^^^^^^^^^^^\u001b[0m \u001b[94mexpected &str, found struct `std::string::String`\u001b[0m",
+      "mismatched types"
+     ]
+    }
+   ],
+   "source": [
+    "let mut s = String::new();\n",
+    "s.push_str(format!(\"foo {}\", 42));"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Seeing what variables have been defined\n",
+    "We can print a table of defined variables and their types with the :vars command."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<table><tr><th>Variable</th><th>Type</th></tr><tr><td>counter</td><td>std::sync::Arc&lt;std::sync::Mutex&lt;i32&gt;&gt;</td><tr><tr><td>message</td><td>String</td><tr><tr><td>m</td><td>user_code_13::Matrix&lt;i32&gt;</td><tr><tr><td>values</td><td>std::vec::Vec&lt;i32&gt;</td><tr></table>"
+      ],
+      "text/plain": [
+       "counter: std::sync::Arc<std::sync::Mutex<i32>>\n",
+       "message: String\n",
+       "m: user_code_13::Matrix<i32>\n",
+       "values: std::vec::Vec<i32>\n"
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    ":vars"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Other built-in commands can be found via :help"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       ":vars             List bound variables and their types\n",
+       ":opt [level]      Toggle/set optimization level\n",
+       ":explain          Print explanation of last error\n",
+       ":clear            Clear all state, keeping compilation cache\n",
+       ":dep              Add dependency. e.g. :dep regex = \"1.0\"\n",
+       ":version          Print Evcxr version\n",
+       ":preserve_vars_on_panic [0|1]  Try to keep vars on panic\n",
+       "\n",
+       "Mostly for development / debugging purposes:\n",
+       ":last_compile_dir Print the directory in which we last compiled\n",
+       ":timing           Toggle printing of how long evaluations take\n",
+       ":last_error_json  Print the last compilation error as JSON (for debugging)\n",
+       ":time_passes      Toggle printing of rustc pass times (requires nightly)\n",
+       ":internal_debug   Toggle various internal debugging code\n"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    ":help"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Rust",
+   "language": "rust",
+   "name": "rust"
+  },
+  "language_info": {
+   "codemirror_mode": "rust",
+   "file_extension": ".rs",
+   "mimetype": "text/rust",
+   "name": "Rust",
+   "pygment_lexer": "rust",
+   "version": ""
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/notebooks/mirror/ipynb_to_Rmd/evcxr_jupyter_tour.Rmd b/tests/notebooks/mirror/ipynb_to_Rmd/evcxr_jupyter_tour.Rmd
new file mode 100644
index 00000000..c948f1cd
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_Rmd/evcxr_jupyter_tour.Rmd
@@ -0,0 +1,183 @@
+---
+jupyter:
+  kernelspec:
+    display_name: Rust
+    language: rust
+    name: rust
+---
+
+# Tour of the EvCxR Jupyter Kernel
+For those not already familiar with Jupyter notebook, it lets you write code into "cells" like the box below. Cells can alternatively contain markdown, like this text here. Each code cell is compiled and executed separately, but variables, defined functions etc persist between cells.
+
+## Printing to outputs and evaluating expressions
+Lets print something to stdout and stderr then return a final expression to see how that's presented. Note that stdout and stderr are separate streams, so may not appear in the same order is their respective print statements.
+
+```{rust}
+println!("Hello world");
+eprintln!("Hello error");
+format!("Hello {}", "world")
+```
+
+## Assigning and making use of variables
+We define a variable `message`, then in the subsequent cell, modify the string and finally print it out. We could also do all this in the one cell if we wanted.
+
+```{rust}
+let mut message = "Hello ".to_owned();
+```
+
+```{rust}
+message.push_str("world!");
+```
+
+```{rust}
+message
+```
+
+## Defining and redefining functions
+Next we'll define a function
+
+```{rust}
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}
+}
+```
+
+```{rust}
+(1..13).map(fib).collect::<Vec<i32>>()
+```
+
+Hmm, that doesn't look right. Lets redefine the function. In practice, we'd go back and edit the function above and reevalute it, but here, lets redefine it in a separate cell.
+
+```{rust}
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {2} else {fib(x - 2) + fib(x - 1)}
+}
+```
+
+```{rust}
+let values = (1..13).map(fib).collect::<Vec<i32>>();
+values
+```
+
+## Spawning a separate thread and communicating with it
+We can spawn a thread to do stuff in the background, then continue executing code in other cells.
+
+```{rust}
+use std::sync::{Mutex, Arc};
+let counter = Arc::new(Mutex::new(0i32));
+std::thread::spawn({
+    let counter = Arc::clone(&counter);
+    move || {
+        for i in 1..300 {
+            *counter.lock().unwrap() += 1;
+            std::thread::sleep(std::time::Duration::from_millis(100));
+        }
+}});
+```
+
+```{rust}
+*counter.lock().unwrap()
+```
+
+```{rust}
+*counter.lock().unwrap()
+```
+
+## Loading external crates
+We can load external crates. This one takes a while to compile, but once it's compiled, subsequent cells shouldn't need to recompile it, so it should be much quicker.
+
+```{rust}
+// :dep base64 = "0.10.1"
+base64::encode(&vec![1, 2, 3, 4])
+```
+
+## Customizing how types are displayed
+We can also customize how our types are displayed, including presenting them as HTML. Here's an example where we define a custom display function for a type `Matrix`.
+
+```{rust}
+use std::fmt::Debug;
+pub struct Matrix<T> {pub values: Vec<T>, pub row_size: usize}
+impl<T: Debug> Matrix<T> {
+    pub fn evcxr_display(&self) {
+        let mut html = String::new();
+        html.push_str("<table>");
+        for r in 0..(self.values.len() / self.row_size) {
+            html.push_str("<tr>");
+            for c in 0..self.row_size {
+                html.push_str("<td>");
+                html.push_str(&format!("{:?}", self.values[r * self.row_size + c]));
+                html.push_str("</td>");
+            }
+            html.push_str("</tr>");            
+        }
+        html.push_str("</table>");
+        println!("EVCXR_BEGIN_CONTENT text/html\n{}\nEVCXR_END_CONTENT", html);
+    }
+}
+```
+
+```{rust}
+let m = Matrix {values: vec![1,2,3,4,5,6,7,8,9], row_size: 3};
+m
+```
+
+We can also return images, we just need to base64 encode them. First, we set up code for displaying RGB and grayscale images.
+
+```{rust}
+extern crate image;
+extern crate base64;
+pub trait EvcxrResult {fn evcxr_display(&self);}
+impl EvcxrResult for image::RgbImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::RGB(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+impl EvcxrResult for image::GrayImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::Gray(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+```
+
+```{rust}
+image::ImageBuffer::from_fn(256, 256, |x, y| {
+    if (x as i32 - y as i32).abs() < 3 {
+        image::Rgb([0, 0, 255])
+    } else {
+        image::Rgb([0, 0, 0])
+    }
+})
+```
+
+## Display of compilation errors
+Here's how compilation errors are presented. Here we forgot an & and passed a String instead of an &str.
+
+```{rust}
+let mut s = String::new();
+s.push_str(format!("foo {}", 42));
+```
+
+## Seeing what variables have been defined
+We can print a table of defined variables and their types with the :vars command.
+
+```{rust}
+// :vars
+```
+
+Other built-in commands can be found via :help
+
+```{rust}
+// :help
+```
+
+```{rust}
+
+```
diff --git a/tests/notebooks/mirror/ipynb_to_md/evcxr_jupyter_tour.md b/tests/notebooks/mirror/ipynb_to_md/evcxr_jupyter_tour.md
new file mode 100644
index 00000000..722a9b1b
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_md/evcxr_jupyter_tour.md
@@ -0,0 +1,183 @@
+---
+jupyter:
+  kernelspec:
+    display_name: Rust
+    language: rust
+    name: rust
+---
+
+# Tour of the EvCxR Jupyter Kernel
+For those not already familiar with Jupyter notebook, it lets you write code into "cells" like the box below. Cells can alternatively contain markdown, like this text here. Each code cell is compiled and executed separately, but variables, defined functions etc persist between cells.
+
+## Printing to outputs and evaluating expressions
+Lets print something to stdout and stderr then return a final expression to see how that's presented. Note that stdout and stderr are separate streams, so may not appear in the same order is their respective print statements.
+
+```rust
+println!("Hello world");
+eprintln!("Hello error");
+format!("Hello {}", "world")
+```
+
+## Assigning and making use of variables
+We define a variable `message`, then in the subsequent cell, modify the string and finally print it out. We could also do all this in the one cell if we wanted.
+
+```rust
+let mut message = "Hello ".to_owned();
+```
+
+```rust
+message.push_str("world!");
+```
+
+```rust
+message
+```
+
+## Defining and redefining functions
+Next we'll define a function
+
+```rust
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}
+}
+```
+
+```rust
+(1..13).map(fib).collect::<Vec<i32>>()
+```
+
+Hmm, that doesn't look right. Lets redefine the function. In practice, we'd go back and edit the function above and reevalute it, but here, lets redefine it in a separate cell.
+
+```rust
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {2} else {fib(x - 2) + fib(x - 1)}
+}
+```
+
+```rust
+let values = (1..13).map(fib).collect::<Vec<i32>>();
+values
+```
+
+## Spawning a separate thread and communicating with it
+We can spawn a thread to do stuff in the background, then continue executing code in other cells.
+
+```rust
+use std::sync::{Mutex, Arc};
+let counter = Arc::new(Mutex::new(0i32));
+std::thread::spawn({
+    let counter = Arc::clone(&counter);
+    move || {
+        for i in 1..300 {
+            *counter.lock().unwrap() += 1;
+            std::thread::sleep(std::time::Duration::from_millis(100));
+        }
+}});
+```
+
+```rust
+*counter.lock().unwrap()
+```
+
+```rust
+*counter.lock().unwrap()
+```
+
+## Loading external crates
+We can load external crates. This one takes a while to compile, but once it's compiled, subsequent cells shouldn't need to recompile it, so it should be much quicker.
+
+```rust
+:dep base64 = "0.10.1"
+base64::encode(&vec![1, 2, 3, 4])
+```
+
+## Customizing how types are displayed
+We can also customize how our types are displayed, including presenting them as HTML. Here's an example where we define a custom display function for a type `Matrix`.
+
+```rust
+use std::fmt::Debug;
+pub struct Matrix<T> {pub values: Vec<T>, pub row_size: usize}
+impl<T: Debug> Matrix<T> {
+    pub fn evcxr_display(&self) {
+        let mut html = String::new();
+        html.push_str("<table>");
+        for r in 0..(self.values.len() / self.row_size) {
+            html.push_str("<tr>");
+            for c in 0..self.row_size {
+                html.push_str("<td>");
+                html.push_str(&format!("{:?}", self.values[r * self.row_size + c]));
+                html.push_str("</td>");
+            }
+            html.push_str("</tr>");            
+        }
+        html.push_str("</table>");
+        println!("EVCXR_BEGIN_CONTENT text/html\n{}\nEVCXR_END_CONTENT", html);
+    }
+}
+```
+
+```rust
+let m = Matrix {values: vec![1,2,3,4,5,6,7,8,9], row_size: 3};
+m
+```
+
+We can also return images, we just need to base64 encode them. First, we set up code for displaying RGB and grayscale images.
+
+```rust
+extern crate image;
+extern crate base64;
+pub trait EvcxrResult {fn evcxr_display(&self);}
+impl EvcxrResult for image::RgbImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::RGB(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+impl EvcxrResult for image::GrayImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::Gray(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+```
+
+```rust
+image::ImageBuffer::from_fn(256, 256, |x, y| {
+    if (x as i32 - y as i32).abs() < 3 {
+        image::Rgb([0, 0, 255])
+    } else {
+        image::Rgb([0, 0, 0])
+    }
+})
+```
+
+## Display of compilation errors
+Here's how compilation errors are presented. Here we forgot an & and passed a String instead of an &str.
+
+```rust
+let mut s = String::new();
+s.push_str(format!("foo {}", 42));
+```
+
+## Seeing what variables have been defined
+We can print a table of defined variables and their types with the :vars command.
+
+```rust
+:vars
+```
+
+Other built-in commands can be found via :help
+
+```rust
+:help
+```
+
+```rust
+
+```
diff --git a/tests/notebooks/mirror/ipynb_to_percent/evcxr_jupyter_tour.rs b/tests/notebooks/mirror/ipynb_to_percent/evcxr_jupyter_tour.rs
new file mode 100644
index 00000000..7e221f57
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_percent/evcxr_jupyter_tour.rs
@@ -0,0 +1,173 @@
+// ---
+// jupyter:
+//   kernelspec:
+//     display_name: Rust
+//     language: rust
+//     name: rust
+// ---
+
+// %% [markdown]
+// # Tour of the EvCxR Jupyter Kernel
+// For those not already familiar with Jupyter notebook, it lets you write code into "cells" like the box below. Cells can alternatively contain markdown, like this text here. Each code cell is compiled and executed separately, but variables, defined functions etc persist between cells.
+//
+// ## Printing to outputs and evaluating expressions
+// Lets print something to stdout and stderr then return a final expression to see how that's presented. Note that stdout and stderr are separate streams, so may not appear in the same order is their respective print statements.
+
+// %%
+println!("Hello world");
+eprintln!("Hello error");
+format!("Hello {}", "world")
+
+// %% [markdown]
+// ## Assigning and making use of variables
+// We define a variable `message`, then in the subsequent cell, modify the string and finally print it out. We could also do all this in the one cell if we wanted.
+
+// %%
+let mut message = "Hello ".to_owned();
+
+// %%
+message.push_str("world!");
+
+// %%
+message
+
+// %% [markdown]
+// ## Defining and redefining functions
+// Next we'll define a function
+
+// %%
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}
+}
+
+// %%
+(1..13).map(fib).collect::<Vec<i32>>()
+
+// %% [markdown]
+// Hmm, that doesn't look right. Lets redefine the function. In practice, we'd go back and edit the function above and reevalute it, but here, lets redefine it in a separate cell.
+
+// %%
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {2} else {fib(x - 2) + fib(x - 1)}
+}
+
+// %%
+let values = (1..13).map(fib).collect::<Vec<i32>>();
+values
+
+// %% [markdown]
+// ## Spawning a separate thread and communicating with it
+// We can spawn a thread to do stuff in the background, then continue executing code in other cells.
+
+// %%
+use std::sync::{Mutex, Arc};
+let counter = Arc::new(Mutex::new(0i32));
+std::thread::spawn({
+    let counter = Arc::clone(&counter);
+    move || {
+        for i in 1..300 {
+            *counter.lock().unwrap() += 1;
+            std::thread::sleep(std::time::Duration::from_millis(100));
+        }
+}});
+
+// %%
+*counter.lock().unwrap()
+
+// %%
+*counter.lock().unwrap()
+
+// %% [markdown]
+// ## Loading external crates
+// We can load external crates. This one takes a while to compile, but once it's compiled, subsequent cells shouldn't need to recompile it, so it should be much quicker.
+
+// %%
+// :dep base64 = "0.10.1"
+base64::encode(&vec![1, 2, 3, 4])
+
+// %% [markdown]
+// ## Customizing how types are displayed
+// We can also customize how our types are displayed, including presenting them as HTML. Here's an example where we define a custom display function for a type `Matrix`.
+
+// %%
+use std::fmt::Debug;
+pub struct Matrix<T> {pub values: Vec<T>, pub row_size: usize}
+impl<T: Debug> Matrix<T> {
+    pub fn evcxr_display(&self) {
+        let mut html = String::new();
+        html.push_str("<table>");
+        for r in 0..(self.values.len() / self.row_size) {
+            html.push_str("<tr>");
+            for c in 0..self.row_size {
+                html.push_str("<td>");
+                html.push_str(&format!("{:?}", self.values[r * self.row_size + c]));
+                html.push_str("</td>");
+            }
+            html.push_str("</tr>");            
+        }
+        html.push_str("</table>");
+        println!("EVCXR_BEGIN_CONTENT text/html\n{}\nEVCXR_END_CONTENT", html);
+    }
+}
+
+// %%
+let m = Matrix {values: vec![1,2,3,4,5,6,7,8,9], row_size: 3};
+m
+
+// %% [markdown]
+// We can also return images, we just need to base64 encode them. First, we set up code for displaying RGB and grayscale images.
+
+// %%
+extern crate image;
+extern crate base64;
+pub trait EvcxrResult {fn evcxr_display(&self);}
+impl EvcxrResult for image::RgbImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::RGB(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+impl EvcxrResult for image::GrayImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::Gray(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+
+// %%
+image::ImageBuffer::from_fn(256, 256, |x, y| {
+    if (x as i32 - y as i32).abs() < 3 {
+        image::Rgb([0, 0, 255])
+    } else {
+        image::Rgb([0, 0, 0])
+    }
+})
+
+// %% [markdown]
+// ## Display of compilation errors
+// Here's how compilation errors are presented. Here we forgot an & and passed a String instead of an &str.
+
+// %%
+let mut s = String::new();
+s.push_str(format!("foo {}", 42));
+
+// %% [markdown]
+// ## Seeing what variables have been defined
+// We can print a table of defined variables and their types with the :vars command.
+
+// %%
+// :vars
+
+// %% [markdown]
+// Other built-in commands can be found via :help
+
+// %%
+// :help
+
+// %%
diff --git a/tests/notebooks/mirror/ipynb_to_script/evcxr_jupyter_tour.rs b/tests/notebooks/mirror/ipynb_to_script/evcxr_jupyter_tour.rs
new file mode 100644
index 00000000..88ec140f
--- /dev/null
+++ b/tests/notebooks/mirror/ipynb_to_script/evcxr_jupyter_tour.rs
@@ -0,0 +1,143 @@
+// ---
+// jupyter:
+//   kernelspec:
+//     display_name: Rust
+//     language: rust
+//     name: rust
+// ---
+
+// # Tour of the EvCxR Jupyter Kernel
+// For those not already familiar with Jupyter notebook, it lets you write code into "cells" like the box below. Cells can alternatively contain markdown, like this text here. Each code cell is compiled and executed separately, but variables, defined functions etc persist between cells.
+//
+// ## Printing to outputs and evaluating expressions
+// Lets print something to stdout and stderr then return a final expression to see how that's presented. Note that stdout and stderr are separate streams, so may not appear in the same order is their respective print statements.
+
+println!("Hello world");
+eprintln!("Hello error");
+format!("Hello {}", "world")
+
+// ## Assigning and making use of variables
+// We define a variable `message`, then in the subsequent cell, modify the string and finally print it out. We could also do all this in the one cell if we wanted.
+
+let mut message = "Hello ".to_owned();
+
+message.push_str("world!");
+
+message
+
+// ## Defining and redefining functions
+// Next we'll define a function
+
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}
+}
+
+(1..13).map(fib).collect::<Vec<i32>>()
+
+// Hmm, that doesn't look right. Lets redefine the function. In practice, we'd go back and edit the function above and reevalute it, but here, lets redefine it in a separate cell.
+
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {2} else {fib(x - 2) + fib(x - 1)}
+}
+
+let values = (1..13).map(fib).collect::<Vec<i32>>();
+values
+
+// ## Spawning a separate thread and communicating with it
+// We can spawn a thread to do stuff in the background, then continue executing code in other cells.
+
+use std::sync::{Mutex, Arc};
+let counter = Arc::new(Mutex::new(0i32));
+std::thread::spawn({
+    let counter = Arc::clone(&counter);
+    move || {
+        for i in 1..300 {
+            *counter.lock().unwrap() += 1;
+            std::thread::sleep(std::time::Duration::from_millis(100));
+        }
+}});
+
+*counter.lock().unwrap()
+
+*counter.lock().unwrap()
+
+// ## Loading external crates
+// We can load external crates. This one takes a while to compile, but once it's compiled, subsequent cells shouldn't need to recompile it, so it should be much quicker.
+
+// :dep base64 = "0.10.1"
+base64::encode(&vec![1, 2, 3, 4])
+
+// ## Customizing how types are displayed
+// We can also customize how our types are displayed, including presenting them as HTML. Here's an example where we define a custom display function for a type `Matrix`.
+
+use std::fmt::Debug;
+pub struct Matrix<T> {pub values: Vec<T>, pub row_size: usize}
+impl<T: Debug> Matrix<T> {
+    pub fn evcxr_display(&self) {
+        let mut html = String::new();
+        html.push_str("<table>");
+        for r in 0..(self.values.len() / self.row_size) {
+            html.push_str("<tr>");
+            for c in 0..self.row_size {
+                html.push_str("<td>");
+                html.push_str(&format!("{:?}", self.values[r * self.row_size + c]));
+                html.push_str("</td>");
+            }
+            html.push_str("</tr>");            
+        }
+        html.push_str("</table>");
+        println!("EVCXR_BEGIN_CONTENT text/html\n{}\nEVCXR_END_CONTENT", html);
+    }
+}
+
+let m = Matrix {values: vec![1,2,3,4,5,6,7,8,9], row_size: 3};
+m
+
+// We can also return images, we just need to base64 encode them. First, we set up code for displaying RGB and grayscale images.
+
+extern crate image;
+extern crate base64;
+pub trait EvcxrResult {fn evcxr_display(&self);}
+impl EvcxrResult for image::RgbImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::RGB(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+impl EvcxrResult for image::GrayImage {
+    fn evcxr_display(&self) {
+        let mut buffer = Vec::new();
+        image::png::PNGEncoder::new(&mut buffer).encode(&**self, self.width(), self.height(),
+            image::ColorType::Gray(8)).unwrap();
+        let img = base64::encode(&buffer);
+        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", img);        
+    }
+}
+
+image::ImageBuffer::from_fn(256, 256, |x, y| {
+    if (x as i32 - y as i32).abs() < 3 {
+        image::Rgb([0, 0, 255])
+    } else {
+        image::Rgb([0, 0, 0])
+    }
+})
+
+// ## Display of compilation errors
+// Here's how compilation errors are presented. Here we forgot an & and passed a String instead of an &str.
+
+let mut s = String::new();
+s.push_str(format!("foo {}", 42));
+
+// ## Seeing what variables have been defined
+// We can print a table of defined variables and their types with the :vars command.
+
+// :vars
+
+// Other built-in commands can be found via :help
+
+// :help
+
+
diff --git a/tests/test_mirror.py b/tests/test_mirror.py
index 6b0fa3c1..33cbe48e 100644
--- a/tests/test_mirror.py
+++ b/tests/test_mirror.py
@@ -321,3 +321,13 @@ def test_ipynb_to_scala(nb_file, no_jupytext_version_number):
 @pytest.mark.parametrize('nb_file', list_notebooks('ipynb_scala'))
 def test_ipynb_to_scala_percent(nb_file, no_jupytext_version_number):
     assert_conversion_same_as_mirror(nb_file, 'scala:percent', 'ipynb_to_percent')
+
+
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_rust'))
+def test_ipynb_to_rust(nb_file, no_jupytext_version_number):
+    assert_conversion_same_as_mirror(nb_file, 'rs', 'ipynb_to_script')
+
+
+@pytest.mark.parametrize('nb_file', list_notebooks('ipynb_rust'))
+def test_ipynb_to_rust_percent(nb_file, no_jupytext_version_number):
+    assert_conversion_same_as_mirror(nb_file, 'rs:percent', 'ipynb_to_percent')
diff --git a/tests/test_read_simple_rust.py b/tests/test_read_simple_rust.py
new file mode 100644
index 00000000..b8c1b1f6
--- /dev/null
+++ b/tests/test_read_simple_rust.py
@@ -0,0 +1,45 @@
+from nbformat.v4.nbbase import new_notebook, new_markdown_cell, new_code_cell
+import jupytext
+from jupytext.compare import compare, compare_notebooks
+
+
+def test_read_magics(text="// :vars\n"):
+    nb = jupytext.reads(text, 'rs')
+    compare_notebooks(nb, new_notebook(cells=[new_code_cell(':vars')]))
+    compare(jupytext.writes(nb, 'rs'), text)
+
+
+def test_read_simple_file(text='''println!("Hello world");
+eprintln!("Hello error");
+format!("Hello {}", "world")
+
+// A Function
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}
+}
+
+// This is a
+// Markdown cell
+
+// This is a magic instruction
+// :vars
+
+// This is a rust identifier
+::std::mem::drop
+'''):
+    nb = jupytext.reads(text, 'rs')
+    compare_notebooks(nb, new_notebook(cells=[
+        new_code_cell('''println!("Hello world");
+eprintln!("Hello error");
+format!("Hello {}", "world")'''),
+        new_code_cell('''// A Function
+pub fn fib(x: i32) -> i32 {
+    if x <= 2 {0} else {fib(x - 2) + fib(x - 1)}
+}'''),
+        new_markdown_cell("This is a\nMarkdown cell"),
+        new_code_cell('''// This is a magic instruction
+:vars'''),
+        new_code_cell('''// This is a rust identifier
+::std::mem::drop''')
+    ]))
+    compare(jupytext.writes(nb, 'rs'), text)