├── .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 [](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 |
--------------------------------------------------------------------------------