├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── deps_example.ipynb └── funcs_example.ipynb ├── example.ipynb ├── img ├── collapsed.png ├── debug1.png ├── deps.png ├── external-crate.png ├── rust-magic.png └── rust_fn.png ├── requirements.txt ├── rust_magic └── __init__.py ├── setup.py └── tests ├── deps1.deps ├── deps1.rs ├── deps1.txt ├── deps2.deps ├── deps2.rs ├── deps2.txt ├── four_cell.rs ├── four_cell.txt ├── four_line.rs ├── four_line.txt ├── hello_cell.rs ├── hello_cell.txt ├── hello_line.rs ├── hello_line.txt ├── hello_main.rs ├── hello_main.txt ├── time_cell.rs ├── time_cell.txt ├── time_main.rs ├── time_main.txt ├── trailing_bracket.rs ├── trailing_bracket.txt ├── trailing_comment.rs ├── trailing_comment.txt ├── trailing_comment1.rs └── trailing_comment1.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /rust_magic.egg-info -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.6" 6 | - "3.7" 7 | script: python rust_magic/__init__.py 8 | branches: 9 | only: 10 | - master 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Lev Maximov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-magic [![Build Status](https://travis-ci.org/axil/rust-magic.svg?branch=master)](https://travis-ci.org/axil/rust-magic) 2 | 3 | Allows to try rust in Jupyter notebook. Implemented via line/cell magics: 4 | 5 | 6 | 7 | ## Installation 8 | 9 | 1. Install rust and jupyter-notebook 10 | 11 | 2. `cargo install cargo-script` 12 | 13 | 3. `pip install rust-magic` 14 | 15 | 4. Enjoy :) 16 | 17 | ## Third-party crates 18 | 19 | are supported via normal `cargo script` syntax (see below for a more compact notation): 20 | 21 | 22 | 23 | ## Compiler options 24 | 25 | can be provided in the cell mode: 26 | 27 | 28 | 29 | NB [Here's](https://nbviewer.jupyter.org/github/axil/rust-magic/blob/master/example.ipynb) a copy-pastable form of all the examples above. 30 | 31 | ## Syntax highlighting 32 | 33 | To enable rust syntax highlighting in %%rust cells run the following snippet in a python jupyter cell: 34 | ``` 35 | from notebook.services.config import ConfigManager 36 | c = ConfigManager() 37 | c.update('notebook', {"CodeCell": {"highlight_modes": {"text/x-rustsrc": {"reg": ["^%%rust"]}}}}) 38 | ``` 39 | 40 | This only needs to be run once: it stores the setting in a config file in home directory. 41 | 42 | ## Long cells 43 | 44 | Jupyter "doesn't like" long cells: when a cell gets longer than the screen its output is not readily visible. 45 | Here're a few ways how to handle the problem with rust_magic: 46 | 47 | a) putting dependencies into a separate cell ([more](https://nbviewer.jupyter.org/github/axil/rust-magic/blob/master/docs/deps_example.ipynb)) 48 | 49 | 50 | b) collapsing function bodies with codefolding jupyter extension 51 | 52 | 53 | c) putting function definitions into separate cells ([more](https://nbviewer.jupyter.org/github/axil/rust-magic/blob/master/docs/funcs_example.ipynb)) 54 | 55 | 56 | ## Faster compile times 57 | 58 | can be acheived by caching dependencies compile results with [sccache](https://github.com/mozilla/sccache). 59 | Rust-magic automatically uses it if it is installed in the system (`cargo install sccache`). 60 | -------------------------------------------------------------------------------- /docs/deps_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext rust_magic" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "Deps can be separated into separate cells:" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 3, 22 | "metadata": {}, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "Deps: time = \"0.1.42\"\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "%rust_deps time = \"0.1.42\"" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 4, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | " Updating crates.io index\n", 46 | " Compiling winapi v0.3.8\n", 47 | " Compiling time v0.1.42\n", 48 | " Compiling cell v0.1.0 (C:\\Users\\ASUS\\AppData\\Local\\Cargo\\script-cache\\file-cell-732c7979eb7da673)\n", 49 | " Finished release [optimized] target(s) in 18.28s\n", 50 | "\"Sat, 31 Aug 2019 03:29:54 +0700\"\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "%rust time::now().rfc822z().to_string()" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "Items too long to fit into a line can be put into a cell: " 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 5, 68 | "metadata": {}, 69 | "outputs": [ 70 | { 71 | "name": "stdout", 72 | "output_type": "stream", 73 | "text": [ 74 | "Deps: time = \"0.1.42\"; ndarray = {git = \"https://github.com/rust-ndarray/ndarray.git\"}\n" 75 | ] 76 | } 77 | ], 78 | "source": [ 79 | "%%rust_deps\n", 80 | "time = \"0.1.42\"\n", 81 | "ndarray = {git = \"https://github.com/rust-ndarray/ndarray.git\"}" 82 | ] 83 | }, 84 | { 85 | "cell_type": "code", 86 | "execution_count": 5, 87 | "metadata": {}, 88 | "outputs": [ 89 | { 90 | "name": "stdout", 91 | "output_type": "stream", 92 | "text": [ 93 | "(\"Mon, 26 Aug 2019 17:12:58 +0700\", 6.0)\n" 94 | ] 95 | } 96 | ], 97 | "source": [ 98 | "%%rust\n", 99 | "(time::now().rfc822z().to_string(), \n", 100 | " ndarray::Array::from_vec(vec![1., 2., 3.]).sum())" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 6, 106 | "metadata": {}, 107 | "outputs": [ 108 | { 109 | "name": "stdout", 110 | "output_type": "stream", 111 | "text": [ 112 | "Deps: time = \"0.1.42\"; ndarray = \"0.12.1\"\n" 113 | ] 114 | } 115 | ], 116 | "source": [ 117 | "%rust_deps" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": 7, 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "name": "stdout", 127 | "output_type": "stream", 128 | "text": [ 129 | "Deps: \n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "%rust_deps --clear" 135 | ] 136 | } 137 | ], 138 | "metadata": { 139 | "kernelspec": { 140 | "display_name": "Python 3", 141 | "language": "python", 142 | "name": "python3" 143 | }, 144 | "language_info": { 145 | "codemirror_mode": { 146 | "name": "ipython", 147 | "version": 3 148 | }, 149 | "file_extension": ".py", 150 | "mimetype": "text/x-python", 151 | "name": "python", 152 | "nbconvert_exporter": "python", 153 | "pygments_lexer": "ipython3", 154 | "version": "3.7.3" 155 | }, 156 | "toc": { 157 | "base_numbering": 1, 158 | "nav_menu": {}, 159 | "number_sections": true, 160 | "sideBar": true, 161 | "skip_h1_title": false, 162 | "title_cell": "Table of Contents", 163 | "title_sidebar": "Contents", 164 | "toc_cell": false, 165 | "toc_position": {}, 166 | "toc_section_display": true, 167 | "toc_window_display": false 168 | } 169 | }, 170 | "nbformat": 4, 171 | "nbformat_minor": 2 172 | } 173 | -------------------------------------------------------------------------------- /docs/funcs_example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext rust_magic" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "### rust_fn command" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "The following chunk of code" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 2, 29 | "metadata": { 30 | "code_folding": [ 31 | 1 32 | ] 33 | }, 34 | "outputs": [ 35 | { 36 | "name": "stdout", 37 | "output_type": "stream", 38 | "text": [ 39 | "5\n" 40 | ] 41 | } 42 | ], 43 | "source": [ 44 | "%%rust\n", 45 | "fn add(a: i32, b: i32) -> i32 { \n", 46 | " a + b \n", 47 | "}\n", 48 | "add(2, 3)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "can be separated into two cells:" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 3, 61 | "metadata": { 62 | "code_folding": [] 63 | }, 64 | "outputs": [ 65 | { 66 | "name": "stdout", 67 | "output_type": "stream", 68 | "text": [ 69 | "Funcs: ['add']\n" 70 | ] 71 | } 72 | ], 73 | "source": [ 74 | "%%rust_fn add\n", 75 | "fn add(a: i32, b: i32) -> i32 { \n", 76 | " a + b \n", 77 | "}" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 4, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "5\n" 90 | ] 91 | } 92 | ], 93 | "source": [ 94 | "%rust add(2, 3)" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "Identificator in the first line of the cell must not necessarily match function name. \n", 102 | "It is used to avoid multiple definitions of the same function in the final program code: " 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 5, 108 | "metadata": { 109 | "code_folding": [] 110 | }, 111 | "outputs": [ 112 | { 113 | "name": "stdout", 114 | "output_type": "stream", 115 | "text": [ 116 | "Funcs: ['add']\n" 117 | ] 118 | } 119 | ], 120 | "source": [ 121 | "%%rust_fn add\n", 122 | "fn add(a: i32, b: i32) -> i32 { \n", 123 | " println!(\"adding\")\n", 124 | " a + b \n", 125 | "} // add() function updated" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 6, 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "name": "stdout", 135 | "output_type": "stream", 136 | "text": [ 137 | "Funcs: ['add']\n" 138 | ] 139 | } 140 | ], 141 | "source": [ 142 | "%%rust_fn add\n", 143 | "fn add3(a: i32, b: i32, c: i32) -> i32 {\n", 144 | " a + b + c\n", 145 | "} // add() function is no longer available " 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "It can even be omitted – subsequent `%rust_fn` without arguments will then remove the previous definitions:" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 52, 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "name": "stdout", 162 | "output_type": "stream", 163 | "text": [ 164 | "Funcs: ['add', '']\n" 165 | ] 166 | } 167 | ], 168 | "source": [ 169 | "%%rust_fn\n", 170 | "fn f(x: f64) -> f64 { 2*x }" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 52, 176 | "metadata": {}, 177 | "outputs": [ 178 | { 179 | "name": "stdout", 180 | "output_type": "stream", 181 | "text": [ 182 | "Funcs: ['add', '']\n" 183 | ] 184 | } 185 | ], 186 | "source": [ 187 | "%%rust_fn\n", 188 | "fn f(x: f64) -> f64 { 3*x } // f() updated" 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": 53, 194 | "metadata": {}, 195 | "outputs": [ 196 | { 197 | "name": "stdout", 198 | "output_type": "stream", 199 | "text": [ 200 | "Funcs: ['add', '']\n" 201 | ] 202 | } 203 | ], 204 | "source": [ 205 | "%%rust_fn\n", 206 | "fn g(x: f64) -> f64 { x*x } // f() is no longer available" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "To see a list of the defined functions (of function cell identifiers to be precise) use" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": 48, 219 | "metadata": {}, 220 | "outputs": [ 221 | { 222 | "name": "stdout", 223 | "output_type": "stream", 224 | "text": [ 225 | "Funcs: ['add', '']\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "%rust_fn --list" 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "To clear the list of the defined functions use" 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": 50, 243 | "metadata": {}, 244 | "outputs": [ 245 | { 246 | "name": "stdout", 247 | "output_type": "stream", 248 | "text": [ 249 | "Funcs: []\n" 250 | ] 251 | } 252 | ], 253 | "source": [ 254 | "%rust_fn --clear" 255 | ] 256 | } 257 | ], 258 | "metadata": { 259 | "kernelspec": { 260 | "display_name": "Python 3", 261 | "language": "python", 262 | "name": "python3" 263 | }, 264 | "language_info": { 265 | "codemirror_mode": { 266 | "name": "ipython", 267 | "version": 3 268 | }, 269 | "file_extension": ".py", 270 | "mimetype": "text/x-python", 271 | "name": "python", 272 | "nbconvert_exporter": "python", 273 | "pygments_lexer": "ipython3", 274 | "version": "3.7.2" 275 | }, 276 | "toc": { 277 | "base_numbering": 1, 278 | "nav_menu": {}, 279 | "number_sections": true, 280 | "sideBar": true, 281 | "skip_h1_title": false, 282 | "title_cell": "Table of Contents", 283 | "title_sidebar": "Contents", 284 | "toc_cell": false, 285 | "toc_position": {}, 286 | "toc_section_display": true, 287 | "toc_window_display": false 288 | } 289 | }, 290 | "nbformat": 4, 291 | "nbformat_minor": 2 292 | } 293 | -------------------------------------------------------------------------------- /example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext rust_magic" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "4\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "%rust 2+2" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 3, 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "(2, 10)\n" 39 | ] 40 | } 41 | ], 42 | "source": [ 43 | "%%rust\n", 44 | "let mut a = (2, 3);\n", 45 | "a.1 = 10;\n", 46 | "a" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 4, 52 | "metadata": {}, 53 | "outputs": [ 54 | { 55 | "name": "stdout", 56 | "output_type": "stream", 57 | "text": [ 58 | "Hi, Rust!\n" 59 | ] 60 | } 61 | ], 62 | "source": [ 63 | "%%rust\n", 64 | "fn main() {\n", 65 | " println!(\"Hi, Rust!\");\n", 66 | "}" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 3, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "'//! ```cargo\\n//! [dependencies]\\n//! time = \"0.1.42\"\\n//! ```\\nextern crate time;\\n\\nprintln!(\"{}\", time::now().rfc822z());\\n'\n", 79 | "Thu, 22 Aug 2019 12:17:11 +0700\n" 80 | ] 81 | } 82 | ], 83 | "source": [ 84 | "%%rust\n", 85 | "//! ```cargo\n", 86 | "//! [dependencies]\n", 87 | "//! time = \"0.1.42\"\n", 88 | "//! ```\n", 89 | "extern crate time;\n", 90 | "\n", 91 | "println!(\"{}\", time::now().rfc822z());" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": 6, 97 | "metadata": {}, 98 | "outputs": [ 99 | { 100 | "name": "stdout", 101 | "output_type": "stream", 102 | "text": [ 103 | "thread 'main' panicked at 'attempt to add with overflow', cell.rs:4:1\n", 104 | "note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.\n" 105 | ] 106 | } 107 | ], 108 | "source": [ 109 | "%%rust --debug\n", 110 | "let a: u8 = 255;\n", 111 | "a + 1" 112 | ] 113 | } 114 | ], 115 | "metadata": { 116 | "kernelspec": { 117 | "display_name": "Python 3", 118 | "language": "python", 119 | "name": "python3" 120 | }, 121 | "language_info": { 122 | "codemirror_mode": { 123 | "name": "ipython", 124 | "version": 3 125 | }, 126 | "file_extension": ".py", 127 | "mimetype": "text/x-python", 128 | "name": "python", 129 | "nbconvert_exporter": "python", 130 | "pygments_lexer": "ipython3", 131 | "version": "3.7.2" 132 | }, 133 | "toc": { 134 | "base_numbering": 1, 135 | "nav_menu": {}, 136 | "number_sections": true, 137 | "sideBar": true, 138 | "skip_h1_title": false, 139 | "title_cell": "Table of Contents", 140 | "title_sidebar": "Contents", 141 | "toc_cell": false, 142 | "toc_position": {}, 143 | "toc_section_display": true, 144 | "toc_window_display": false 145 | } 146 | }, 147 | "nbformat": 4, 148 | "nbformat_minor": 2 149 | } 150 | -------------------------------------------------------------------------------- /img/collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axil/rust-magic/43ee897b11bda41d8afccb73ee64e29ae4f1975c/img/collapsed.png -------------------------------------------------------------------------------- /img/debug1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axil/rust-magic/43ee897b11bda41d8afccb73ee64e29ae4f1975c/img/debug1.png -------------------------------------------------------------------------------- /img/deps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axil/rust-magic/43ee897b11bda41d8afccb73ee64e29ae4f1975c/img/deps.png -------------------------------------------------------------------------------- /img/external-crate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axil/rust-magic/43ee897b11bda41d8afccb73ee64e29ae4f1975c/img/external-crate.png -------------------------------------------------------------------------------- /img/rust-magic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axil/rust-magic/43ee897b11bda41d8afccb73ee64e29ae4f1975c/img/rust-magic.png -------------------------------------------------------------------------------- /img/rust_fn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axil/rust-magic/43ee897b11bda41d8afccb73ee64e29ae4f1975c/img/rust_fn.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | IPython -------------------------------------------------------------------------------- /rust_magic/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2019, Lev Maximov 4 | # All rights reserved. 5 | # Distributed under the terms of the MIT license: 6 | # http://www.opensource.org/licenses/MIT 7 | 8 | from __future__ import print_function 9 | from contextlib import contextmanager 10 | import glob 11 | import os 12 | import re 13 | import sys 14 | from textwrap import dedent 15 | try: 16 | from time import perf_counter as clock 17 | except: # python2.7 compatibility 18 | import time 19 | perf_counter = time.clock if sys.platform == "win32" else time.time 20 | from subprocess import PIPE, STDOUT, Popen 21 | import sys 22 | 23 | from IPython.core.magic import (Magics, magics_class, line_magic, 24 | cell_magic, line_cell_magic) 25 | 26 | __version__ = '0.3.2' 27 | 28 | 29 | @contextmanager 30 | def cwd(path): 31 | oldpwd=os.getcwd() 32 | os.chdir(path) 33 | try: 34 | yield 35 | finally: 36 | os.chdir(oldpwd) 37 | 38 | def calc_hash(s): 39 | return ('%04x' % hash(s))[-4:] 40 | 41 | deps_template = dedent('''\ 42 | //! ```cargo 43 | //! [dependencies] 44 | %s 45 | //! ``` 46 | ''') 47 | 48 | print_wrapper = dedent('''\ 49 | #[allow(unused)] 50 | fn main(){ 51 | println!("{:?}", (||{ 52 | %s 53 | })()); 54 | } 55 | '''), ' '*8 56 | 57 | run_wrapper = dedent('''\ 58 | #[allow(unused)] 59 | fn main(){ 60 | %s 61 | } 62 | '''), ' '*4 63 | 64 | def eq(a, b): 65 | if a != b: 66 | print('%s != %s' % (a, b)) 67 | sys.exit(1) 68 | 69 | cur_func_name = lambda n=0: sys._getframe(n + 1).f_code.co_name 70 | 71 | def normalize_dep(dep): 72 | return ' = '.join(re.split('\s*=\s*', dep)) 73 | 74 | def parse_deps_line(s): 75 | return [(k, v if v else '"*"') for (k, v) in 76 | re.findall('\s*([a-zA-Z-]+)\s*(?:=\s*((?:{[^}]*})|(?:"[^"]*")))?', s)] 77 | 78 | def test_parse_deps_line(): 79 | eq(parse_deps_line("ndarray, ndarray-rand, rand"), 80 | [('ndarray', '"*"'), ('ndarray-rand', '"*"'), ('rand', '"*"')]) 81 | eq(parse_deps_line('''\ 82 | ndarray = {git = "https://github.com/rust-ndarray/ndarray.git"}, \ 83 | array="0.12.1"'''), 84 | [('ndarray', '{git = "https://github.com/rust-ndarray/ndarray.git"}'), 85 | ('array', '"0.12.1"')]) 86 | eq(parse_deps_line('ndarray="0.12.1", ndarray-rand="0.9.0", rand="0.7.0"'), 87 | [('ndarray', '"0.12.1"'), ('ndarray-rand', '"0.9.0"'), ('rand', '"0.7.0"')]) 88 | print(cur_func_name() + ' ok') 89 | 90 | def construct_rs(mline, cell, deps={}, funcs={}, feats=[]): 91 | cmd = ['cargo', 'script'] 92 | mline = mline.strip() 93 | body = '' 94 | if deps: 95 | body = deps_template % '\n'.join('//! %s = %s' % d for d in deps.items()) 96 | if feats: 97 | for feat in feats: 98 | body += '#![feature(%s)]\n' % feat 99 | for dep in deps: 100 | body += 'extern crate %s;\n' % dep.replace('-', '_') 101 | elif feats: 102 | for feat in feats: 103 | body += '#![feature(%s)]\n' % feat 104 | if funcs: 105 | body += '\n'.join(funcs.values()) 106 | if cell is None: 107 | if len(mline) > 0 and mline[-1] in ';}': 108 | wrapper = run_wrapper 109 | else: 110 | wrapper = print_wrapper 111 | body += wrapper[0] % (wrapper[1] + mline) 112 | else: 113 | if mline: 114 | cmd.extend(mline.split('//', 1)[0].split()) 115 | if 'fn main(' in cell: 116 | body += cell 117 | else: 118 | cell = cell.rstrip() 119 | lines = cell.split('\n') 120 | for i, line in enumerate(lines): 121 | if line.startswith('//') or \ 122 | line.startswith('extern crate') or \ 123 | line.startswith('use ') or \ 124 | line.strip() == '': 125 | pass 126 | else: 127 | break 128 | wrapper = print_wrapper 129 | for line in reversed(lines[i:]): 130 | line = line.split('//', 1)[0].rstrip() 131 | if not line: 132 | continue 133 | if line[-1] in ';}': 134 | wrapper = run_wrapper 135 | break 136 | body += '\n'.join(lines[:i] + [wrapper[0] % '\n'.join( 137 | wrapper[1] + line for line in lines[i:])]) 138 | return cmd, body 139 | 140 | if sys.version_info[:3] < (3,6,0): 141 | from collections import OrderedDict 142 | odict = OrderedDict 143 | else: 144 | odict = dict 145 | 146 | @magics_class 147 | class MyMagics(Magics): 148 | def __init__(self, *args, **kwargs): 149 | super(MyMagics, self).__init__(*args, **kwargs) 150 | self.work_dir = '.rust_magic' 151 | if not os.path.exists(self.work_dir): 152 | os.mkdir(self.work_dir) 153 | self.deps = odict() 154 | self.funcs = {} 155 | self.feats = [] 156 | # self.temp_dir = tempfile.TemporaryDirectory() 157 | # print('Working dir:', self.temp_dir.name) 158 | 159 | @line_cell_magic 160 | def rust(self, mline, cell=None): 161 | ''' 162 | %rust/%%rust runs code in the line/cell. 163 | %rust -v/--version prints version of rust_magic 164 | ''' 165 | if cell is None and mline.strip() in ('-v', '--version'): 166 | print('rust_magic v%s' % __version__) 167 | return 168 | cmd, body = construct_rs(mline, cell, self.deps, self.funcs, self.feats) 169 | # with cwd(self.work_dir): 170 | #filename = 'cell-%s.rs' % calc_hash(body) 171 | filename = os.path.join(self.work_dir, 'cell.rs') 172 | open(filename, 'wb').write(body.encode('utf8')) 173 | cmd.append(filename) 174 | os.environ['RUSTC_WRAPPER'] = 'sccache' 175 | with Popen(cmd, stdout=PIPE, stderr=STDOUT) as proc: 176 | while True: 177 | line = proc.stdout.readline() 178 | if line: 179 | print(line.decode().rstrip()) 180 | else: 181 | break 182 | 183 | @line_cell_magic 184 | def trust(self, line, cell=None): 185 | ''' 186 | Same as %rust/%%rust but also measures total elapsed time 187 | ''' 188 | t1 = clock() 189 | self.rust(line, cell) 190 | t2 = clock() 191 | print('Took %.0f ms' % ((t2-t1)*1000)) 192 | 193 | @line_cell_magic 194 | def rust_deps(self, line, cell=None): 195 | ''' 196 | One or more dependencies with version numbers separated by semicolon, eg 197 | %rust_deps ndarray = "0.12.1"; glob = "0.3.0" 198 | Use -l/--list to see currently configured deps: 199 | %rust_deps --list 200 | Use -c/--clear to clear the list of deps: 201 | %rust_deps --clear 202 | ''' 203 | chunks = [] 204 | line = line.strip() 205 | only = False 206 | if cell is None: 207 | while True: 208 | parts = line.split(None, 1) 209 | if parts and parts[0].startswith('-'): 210 | if len(parts) == 2: 211 | opt, tail = parts 212 | else: 213 | opt, tail = parts[0], '' 214 | if opt in ('-c', '--clear'): 215 | self.deps.clear() 216 | line = '' 217 | break 218 | elif opt in ['-l', '--list']: 219 | line = '' 220 | break 221 | elif opt in ['-o', '--only']: 222 | only = True 223 | else: 224 | print('Unknown option:', opt) 225 | return 226 | line = tail 227 | else: 228 | break 229 | chunks = ["%s = %s" % (a, b) for a, b in parse_deps_line(line)] 230 | else: 231 | chunks = cell.splitlines() 232 | if chunks: 233 | news = odict(re.split('\s*=\s*', d, 1) for d in chunks if d) 234 | if only: 235 | self.deps = news 236 | else: 237 | self.deps.update(news) 238 | s = (', ' if cell is None else '\n').join(['%s = %s' % (k, v) for k, v in self.deps.items()]) 239 | print('Deps:', s if s else '') 240 | 241 | @line_magic 242 | def rust_feat(self, line): 243 | line = line.strip() 244 | if line in ('-c', '--clear'): 245 | self.feats = [] 246 | elif line not in ('-l', '--list'): 247 | if line not in self.feats: 248 | self.feats.append(line) 249 | print(self.feats) 250 | 251 | @line_cell_magic 252 | def rust_fn(self, line, cell=None): 253 | ''' 254 | Function definitions block with optional name. 255 | -l/--list see currently configured function blocs: 256 | %rust_fn --list 257 | -c/--clear to clear the list of function blocks: 258 | %rust_fn --clear 259 | -b/--build runs current cell after others in the list 260 | -o/--only runs current cell only 261 | ''' 262 | parts = line.split('//', 1)[0].strip().split() 263 | flags, name = [], '' 264 | for p in parts: 265 | if p[0] == '-': 266 | flags.append(p) 267 | else: 268 | name = p 269 | if ('-b' in flags or '--build' in flags) and cell is not None: 270 | if name in self.funcs: 271 | del self.funcs[name] 272 | self.rust('', cell) 273 | elif ('-o' in flags or '--only' in flags) and cell is not None: 274 | backup = self.funcs 275 | self.funcs = {} 276 | self.rust('', cell) 277 | self.funcs = backup 278 | elif '-c' in flags or '--clear' in flags: 279 | self.funcs.clear() 280 | if cell is None: 281 | if '-l' not in flags and '--list' not in flags and name in self.funcs: 282 | del self.funcs[name] 283 | else: 284 | self.funcs[name] = cell 285 | print('Funcs:', list(self.funcs.keys())) 286 | 287 | 288 | def load_ipython_extension(ipython): 289 | ipython.register_magics(MyMagics) 290 | 291 | def parse_deps_cell(s): 292 | return odict(re.split('\s*=\s*', d, 1) for d in s.splitlines() if d) 293 | 294 | def test_construct_rs(): 295 | ok = True 296 | project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 297 | for test_name in glob.glob(os.path.join(project_dir, 'tests', 'deps2.txt')): 298 | fin = open(test_name) 299 | mline = fin.readline() 300 | if '%rust ' in mline: 301 | mline = mline.split('%rust ', 1)[1] 302 | cell = fin.read() or None 303 | deps_fn = re.sub('\.txt', '.deps', test_name) 304 | if os.path.exists(deps_fn): 305 | deps = open(deps_fn).read() 306 | else: 307 | deps = '' 308 | cmd, body = construct_rs(mline, cell, parse_deps_cell(deps)) 309 | expect = open(re.sub('\.txt$', '.rs', test_name)).read() 310 | # if 'four_cell' in test_name: 311 | # import ipdb; ipdb.set_trace() 312 | if body != expect: 313 | print(test_name + ' failed:\n' + repr(body) + '\n!= expected\n' + repr(expect)) 314 | ok = False 315 | else: 316 | sys.stdout.write('.') 317 | sys.stdout.flush() 318 | if ok: 319 | print('\n' + cur_func_name() + ' ok') 320 | else: 321 | sys.exit(1) 322 | 323 | def test_deps(): 324 | m = MyMagics() 325 | m.rust_deps('a="1"') 326 | eq(m.deps, {'a': '"1"'}) 327 | m.rust_deps(' -l ') 328 | eq(m.deps, {'a': '"1"'}) 329 | m.rust_deps('--list') 330 | eq(m.deps, {'a': '"1"'}) 331 | m.rust_deps('', 'a="1"\nb="2"') 332 | eq(m.deps, {'a': '"1"', 'b': '"2"'}) 333 | m.rust_deps('c="3",d={git = "www"}') 334 | eq(m.deps, {'a': '"1"', 'b': '"2"', 'c': '"3"', 'd': '{git = "www"}'}) 335 | m.rust_deps('--clear') 336 | eq(m.deps, {}) 337 | print(cur_func_name() + ' ok') 338 | 339 | def test_funcs(): 340 | m = MyMagics() 341 | m.rust_fn('f', 'f()') 342 | eq(m.funcs, {'f': 'f()'}) 343 | m.rust_fn('-l') 344 | eq(m.funcs, {'f': 'f()'}) 345 | m.rust_fn('--list') 346 | eq(m.funcs, {'f': 'f()'}) 347 | m.rust_fn('f // comment', 'g()') 348 | eq(m.funcs, {'f': 'g()'}) 349 | m.rust_fn('h', 'h()') 350 | eq(m.funcs, {'f': 'g()', 'h': 'h()'}) 351 | m.rust_fn('--clear') 352 | eq(m.funcs, {}) 353 | print(cur_func_name() + ' ok') 354 | 355 | if __name__ == '__main__': 356 | test_parse_deps_line() 357 | test_construct_rs() 358 | test_deps() 359 | test_funcs() 360 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import setuptools 4 | 5 | with open("README.md", "r") as fh: 6 | long_description = fh.read() 7 | 8 | setuptools.setup( 9 | name="rust_magic", 10 | version="0.3.2", 11 | author='Lev Maximov', 12 | author_email='lev.maximov@gmail.com', 13 | url='https://github.com/axil/rust-magic', 14 | description="Lightweight Rust integration in Jupyter notebook", 15 | long_description=long_description, 16 | long_description_content_type="text/markdown", 17 | packages=['rust_magic'], 18 | classifiers=[ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ], 23 | license="MIT License", 24 | zip_safe=False, 25 | keywords=['rust', 'jupyter', 'notebook', 'magic'], 26 | ) 27 | -------------------------------------------------------------------------------- /tests/deps1.deps: -------------------------------------------------------------------------------- 1 | time="0.1.42" -------------------------------------------------------------------------------- /tests/deps1.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! time = "0.1.42" 4 | //! ``` 5 | extern crate time; 6 | #[allow(unused)] 7 | fn main(){ 8 | println!("{:?}", (||{ 9 | time::now().rfc822z().to_string() 10 | })()); 11 | } 12 | -------------------------------------------------------------------------------- /tests/deps1.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | time::now().rfc822z().to_string() 3 | -------------------------------------------------------------------------------- /tests/deps2.deps: -------------------------------------------------------------------------------- 1 | time = "0.1.42" 2 | ndarray = "0.12.1" -------------------------------------------------------------------------------- /tests/deps2.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! time = "0.1.42" 4 | //! ndarray = "0.12.1" 5 | //! ``` 6 | extern crate time; 7 | extern crate ndarray; 8 | #[allow(unused)] 9 | fn main(){ 10 | println!("{:?}", (||{ 11 | (time::now().rfc822z().to_string(), 12 | ndarray::Array::from_vec(vec![1., 2., 3.]).sum()) 13 | })()); 14 | } 15 | -------------------------------------------------------------------------------- /tests/deps2.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | (time::now().rfc822z().to_string(), 3 | ndarray::Array::from_vec(vec![1., 2., 3.]).sum()) 4 | -------------------------------------------------------------------------------- /tests/four_cell.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | println!("{:?}", (||{ 4 | 2+2 5 | })()); 6 | } -------------------------------------------------------------------------------- /tests/four_cell.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | 2+2 3 | -------------------------------------------------------------------------------- /tests/four_line.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | println!("{:?}", (||{ 4 | 2+2 5 | })()); 6 | } -------------------------------------------------------------------------------- /tests/four_line.txt: -------------------------------------------------------------------------------- 1 | %rust 2+2 2 | -------------------------------------------------------------------------------- /tests/hello_cell.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | println!("hi"); 4 | } -------------------------------------------------------------------------------- /tests/hello_cell.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | println!("hi"); 3 | -------------------------------------------------------------------------------- /tests/hello_line.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | println!("hi"); 4 | } -------------------------------------------------------------------------------- /tests/hello_line.txt: -------------------------------------------------------------------------------- 1 | %rust println!("hi"); 2 | -------------------------------------------------------------------------------- /tests/hello_main.rs: -------------------------------------------------------------------------------- 1 | fn main(){ 2 | println!("hi"); 3 | } -------------------------------------------------------------------------------- /tests/hello_main.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | fn main(){ 3 | println!("hi"); 4 | } 5 | -------------------------------------------------------------------------------- /tests/time_cell.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! time = "0.1.42" 4 | //! ``` 5 | extern crate time; 6 | 7 | #[allow(unused)] 8 | fn main(){ 9 | println!("{}", time::now().rfc822z()); 10 | } -------------------------------------------------------------------------------- /tests/time_cell.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | //! ```cargo 3 | //! [dependencies] 4 | //! time = "0.1.42" 5 | //! ``` 6 | extern crate time; 7 | 8 | println!("{}", time::now().rfc822z()); 9 | -------------------------------------------------------------------------------- /tests/time_main.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! time = "0.1.42" 4 | //! ``` 5 | extern crate time; 6 | fn main() { 7 | println!("{}", time::now().rfc822z()); 8 | } -------------------------------------------------------------------------------- /tests/time_main.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | //! ```cargo 3 | //! [dependencies] 4 | //! time = "0.1.42" 5 | //! ``` 6 | extern crate time; 7 | fn main() { 8 | println!("{}", time::now().rfc822z()); 9 | } 10 | -------------------------------------------------------------------------------- /tests/trailing_bracket.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | for i in 1..=5 { 4 | println!("{}", i); 5 | } 6 | } -------------------------------------------------------------------------------- /tests/trailing_bracket.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | for i in 1..=5 { 3 | println!("{}", i); 4 | } 5 | -------------------------------------------------------------------------------- /tests/trailing_comment.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | for i in 1..=5 { 4 | println!("{}", i); 5 | } 6 | // end 7 | } -------------------------------------------------------------------------------- /tests/trailing_comment.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | for i in 1..=5 { 3 | println!("{}", i); 4 | } 5 | // end 6 | -------------------------------------------------------------------------------- /tests/trailing_comment1.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused)] 2 | fn main(){ 3 | for i in 1..=5 { 4 | println!("{}", i); 5 | } // end 6 | } -------------------------------------------------------------------------------- /tests/trailing_comment1.txt: -------------------------------------------------------------------------------- 1 | %%rust 2 | for i in 1..=5 { 3 | println!("{}", i); 4 | } // end 5 | --------------------------------------------------------------------------------