├── .gitignore ├── LICENCE.txt ├── MANIFEST.in ├── README.md ├── docs └── presentation │ ├── An Elm kernel for Jupyter.ipynb │ ├── README.md │ ├── images │ ├── cooper-thumbsup.jpeg │ ├── elm-logo.png │ └── squarelogo-greytext-orangebody-greymoons.png │ ├── requirements.txt │ └── theme │ ├── sixty-north-device.png │ ├── sixty-north-logo.png │ ├── sixty-north-logo.small.png │ └── sixty_north.css ├── elm_kernel ├── __init__.py ├── __main__.py ├── install.py └── kernel.py ├── examples ├── hello-world.ipynb ├── images │ └── tea.png ├── svg │ ├── SVG example.ipynb │ └── elm.json └── the-elm-architecture.ipynb ├── kernel.json └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | MANIFEST 3 | .ipynb_checkpoints 4 | dist 5 | build 6 | .ropeproject 7 | *.egg-info 8 | .vscode 9 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Austin Bingham 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 11 | all 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 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This kernel adds support for Elm to [Jupyter](http://jupyter.org/) notebooks. 2 | 3 | While basic functionality is in place, this is still very much a work in 4 | progress. I'm still figuring it all out. Any help, ideas, etc. would be great. 5 | 6 | # Requirements 7 | - Python 3.6+ 8 | 9 | # Installation 10 | 11 | Either install from a repository using `pip`: 12 | 13 | ``` 14 | pip install elm_kernel 15 | ``` 16 | 17 | or install the package from source: 18 | 19 | ``` 20 | pip install -e . 21 | ``` 22 | 23 | Then install the kernel spec: 24 | ``` 25 | python -m elm_kernel.install 26 | ``` 27 | 28 | # Usage 29 | 30 | Run `jupyter notebook` and select the Elm kernel for a new notebook. 31 | 32 | ## Multi-cell code examples 33 | 34 | By default, when you execute a code cell with the Elm kernel the code will *not* 35 | be compiled. Instead, the kernel simply queues up code cells. This way you can 36 | break longer examples over multiple cells, interleaving the code cells with 37 | supporting Markdown cells. 38 | 39 | In order to ask the kernel to actually compile your code, you need to terminate 40 | a code cell with the line: 41 | 42 | ``` 43 | -- compile-code 44 | ``` 45 | 46 | When the kernel sees a cell like this it contatenates, in cell-execution order, 47 | all of the executed but uncompiled code cells (i.e. everything since the start 48 | of the kernel or the last `-- compile-code` cell). It then compiles the 49 | concatenated code, returning the result to the notebook. 50 | 51 | For a concrete example of this, see 52 | [`examples/the-elm-architecture.ipynb`](https://github.com/abingham/jupyter-elm-kernel/blob/master/examples/the-elm-architecture.ipynb). 53 | 54 | This is a bit hacky, and we're actively searching for a better alternative. 55 | Ideas are welcome! 56 | 57 | # Examples 58 | 59 | The `examples` directory contains a few examples of how to use this kernel. Just 60 | go to that directory and run `jupyter notebook` to see them. 61 | -------------------------------------------------------------------------------- /docs/presentation/An Elm kernel for Jupyter.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 10, 6 | "metadata": { 7 | "collapsed": false, 8 | "deletable": true, 9 | "editable": true, 10 | "slideshow": { 11 | "slide_type": "skip" 12 | } 13 | }, 14 | "outputs": [ 15 | { 16 | "data": { 17 | "text/html": [ 18 | "" 19 | ], 20 | "text/plain": [ 21 | "" 22 | ] 23 | }, 24 | "metadata": {}, 25 | "output_type": "display_data" 26 | } 27 | ], 28 | "source": [ 29 | "%%html\n", 30 | "" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": { 36 | "deletable": true, 37 | "editable": true, 38 | "slideshow": { 39 | "slide_type": "slide" 40 | } 41 | }, 42 | "source": [ 43 | "# The Jupyter Elm Kernel\n", 44 | "## Interactive notebooks for Elm\n", 45 | "\n", 46 | "\n", 47 | "\n", 50 | "\n", 53 | "
\n", 48 | "\"Jupyter\n", 49 | "\n", 51 | "\"Elm\n", 52 | "
\n", 54 | "\n", 55 | "----\n", 56 | "\n", 57 | "**Austin Bingham**
\n", 58 | "*twitter:* @austin_bingham
\n", 59 | "*email:* austin@sixty-north.com\n", 60 | "\n", 61 | "----\n", 62 | "\n", 63 | "![Sixty North logo](theme/sixty-north-logo.small.png)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": { 69 | "deletable": true, 70 | "editable": true, 71 | "slideshow": { 72 | "slide_type": "slide" 73 | } 74 | }, 75 | "source": [ 76 | "# What is a Jupyter notebook?\n", 77 | "\n", 78 | "> The Jupyter Notebook is an open-source web application that allows you to create and share documents that contain live code, equations, visualizations and explanatory text.\n", 79 | ">\n", 80 | "> -- jupyter.org" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": { 86 | "deletable": true, 87 | "editable": true, 88 | "slideshow": { 89 | "slide_type": "fragment" 90 | } 91 | }, 92 | "source": [ 93 | "Let's look at [an example showing the Elm architecture](../../examples/the-elm-architecture.ipynb)." 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": { 99 | "deletable": true, 100 | "editable": true, 101 | "slideshow": { 102 | "slide_type": "slide" 103 | } 104 | }, 105 | "source": [ 106 | "# What is a Jupyter kernel?\n", 107 | "> A ‘kernel’ is a program that runs and introspects the user’s code.\n", 108 | ">\n", 109 | "> -- ipython.org\n", 110 | "\n", 111 | "- Receives code from the client when a cell is executed\n", 112 | "- Runs the code (for some definition of \"run\")\n", 113 | "- Returns output and status of execution" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": { 119 | "deletable": true, 120 | "editable": true, 121 | "slideshow": { 122 | "slide_type": "slide" 123 | } 124 | }, 125 | "source": [ 126 | "# The Elm kernel\n", 127 | "## Support for Elm code cells in Jupyter notebooks\n", 128 | "----\n", 129 | "- Accumulates code cells\n", 130 | "- Compiles accumulated code on \"`-- compile-code`\"\n", 131 | "- Compilation goes into a temporary file\n", 132 | "- Compilaton results are shipped back to the web client\n", 133 | "- `elm-stuff` if kept between compilations" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": { 139 | "deletable": true, 140 | "editable": true, 141 | "slideshow": { 142 | "slide_type": "slide" 143 | } 144 | }, 145 | "source": [ 146 | "# Kernel initialization\n", 147 | "----\n", 148 | "```python\n", 149 | "class ElmKernel(Kernel):\n", 150 | " def __init__(self, *args, **kwargs):\n", 151 | " super().__init__(*args, **kwargs)\n", 152 | " self._code = []\n", 153 | " self._tempdir = TemporaryDirectory()\n", 154 | "```" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": { 160 | "deletable": true, 161 | "editable": true, 162 | "slideshow": { 163 | "slide_type": "slide" 164 | } 165 | }, 166 | "source": [ 167 | "# Compilation and execution\n", 168 | "## Receive code from the web client\n", 169 | "----\n", 170 | "```python\n", 171 | "def do_execute(self, code, . . .):\n", 172 | " self._code.append(code)\n", 173 | " if self._should_compile:\n", 174 | " try:\n", 175 | " code = \"\\n\".join(self._code)\n", 176 | " self._code = []\n", 177 | " self._compile(code)\n", 178 | " except Exception as exc:\n", 179 | " self._send_error_result(str(exc))\n", 180 | " return {'status': 'error' . . . }\n", 181 | " return {'status': 'ok' . . . }\n", 182 | "```" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": { 188 | "deletable": true, 189 | "editable": true, 190 | "slideshow": { 191 | "slide_type": "subslide" 192 | } 193 | }, 194 | "source": [ 195 | "# Compilation and execution\n", 196 | "## Write code to file and compile it, reporting any failures\n", 197 | "----\n", 198 | "```python\n", 199 | "def _compile(self, code):\n", 200 | " with self._tempfile('input.elm') as infile,\\\n", 201 | " self._tempfile('index.js') as outfile:\n", 202 | " with open(infile, mode='wt') as f:\n", 203 | " f.write(code)\n", 204 | " try:\n", 205 | " # compile in a subprocess (next slide)\n", 206 | " except subprocess.CalledProcessError as err:\n", 207 | " self._send_error_result(err.stdout)\n", 208 | " except Exception as err:\n", 209 | " self._send_error_result(repr(err))\n", 210 | " raise\n", 211 | "```" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "metadata": { 217 | "deletable": true, 218 | "editable": true, 219 | "slideshow": { 220 | "slide_type": "subslide" 221 | } 222 | }, 223 | "source": [ 224 | "# Compilation and execution\n", 225 | "## Run compiler, read output, and report success\n", 226 | "----\n", 227 | "```python\n", 228 | "subprocess.run(\n", 229 | " ['elm-make', infile, '--yes', '--output={}'.format(outfile)],\n", 230 | " cwd=self._tempdir.name,\n", 231 | " check=True,\n", 232 | " stdout=subprocess.PIPE,\n", 233 | " stderr=subprocess.STDOUT,\n", 234 | " encoding=sys.getdefaultencoding())\n", 235 | "\n", 236 | "with open(outfile, mode='rt') as f:\n", 237 | " javascript = f.read()\n", 238 | "\n", 239 | "self._send_success_result(javascript)\n", 240 | "```" 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": { 246 | "deletable": true, 247 | "editable": true, 248 | "slideshow": { 249 | "slide_type": "slide" 250 | } 251 | }, 252 | "source": [ 253 | "# Reporting success\n", 254 | "## Producing JavaScript embedding code\n", 255 | "----\n", 256 | "```python\n", 257 | "module_name = \"Main\"\n", 258 | "div_id = 'elm-div-' + str(self.execution_count)\n", 259 | "template = \"\"\"\n", 260 | " var defineElm = function(cb) {{\n", 261 | " if (this.Elm) {{\n", 262 | " this.oldElm = this.Elm;\n", 263 | " }}\n", 264 | " var define = null;\n", 265 | " {js}\n", 266 | " cb();\n", 267 | " }};\n", 268 | " var obj = new Object();\n", 269 | " defineElm.bind(obj)(function(){{\n", 270 | " var mountNode = document.getElementById('{div_id}');\n", 271 | " obj.Elm. {module_name}.embed(mountNode);\n", 272 | " }});\"\"\"\n", 273 | "```" 274 | ] 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "metadata": { 279 | "deletable": true, 280 | "editable": true, 281 | "slideshow": { 282 | "slide_type": "subslide" 283 | } 284 | }, 285 | "source": [ 286 | "# Reporting success\n", 287 | "## Injecting HTML into the client\n", 288 | "----\n", 289 | "```python\n", 290 | "self.send_response(\n", 291 | " self.iopub_socket,\n", 292 | " 'display_data',\n", 293 | " {\n", 294 | " 'metadata': {},\n", 295 | " 'data': {\n", 296 | " 'text/html': '
'\n", 297 | " }\n", 298 | " }\n", 299 | ")\n", 300 | "```" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "metadata": { 306 | "deletable": true, 307 | "editable": true, 308 | "slideshow": { 309 | "slide_type": "subslide" 310 | } 311 | }, 312 | "source": [ 313 | "# Reporting success\n", 314 | "## Sending JavaScript to client\n", 315 | "----\n", 316 | "```python\n", 317 | "javascript = template.format(\n", 318 | " js=javascript,\n", 319 | " module_name=module_name,\n", 320 | " div_id=div_id)\n", 321 | "\n", 322 | "self.send_response(\n", 323 | " self.iopub_socket,\n", 324 | " 'display_data',\n", 325 | " {\n", 326 | " 'metadata': {},\n", 327 | " 'data': {\n", 328 | " 'application/javascript': javascript\n", 329 | " }\n", 330 | " })\n", 331 | "```" 332 | ] 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "metadata": { 337 | "deletable": true, 338 | "editable": true, 339 | "slideshow": { 340 | "slide_type": "slide" 341 | } 342 | }, 343 | "source": [ 344 | "# Good news! It seems to work!\n", 345 | "\n", 346 | "![Agent Cooper giving thumbs up](images/cooper-thumbsup.jpeg)" 347 | ] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "metadata": { 352 | "collapsed": true, 353 | "deletable": true, 354 | "editable": true, 355 | "slideshow": { 356 | "slide_type": "slide" 357 | } 358 | }, 359 | "source": [ 360 | "# Room for improvement\n" 361 | ] 362 | }, 363 | { 364 | "cell_type": "markdown", 365 | "metadata": { 366 | "deletable": true, 367 | "editable": true, 368 | "slideshow": { 369 | "slide_type": "fragment" 370 | } 371 | }, 372 | "source": [ 373 | "- Improved way of signalling for compilation" 374 | ] 375 | }, 376 | { 377 | "cell_type": "markdown", 378 | "metadata": { 379 | "slideshow": { 380 | "slide_type": "fragment" 381 | } 382 | }, 383 | "source": [ 384 | "- Some way to re-use code cells" 385 | ] 386 | }, 387 | { 388 | "cell_type": "markdown", 389 | "metadata": { 390 | "deletable": true, 391 | "editable": true, 392 | "slideshow": { 393 | "slide_type": "fragment" 394 | } 395 | }, 396 | "source": [ 397 | "- Better support for `elm-package.json`" 398 | ] 399 | }, 400 | { 401 | "cell_type": "markdown", 402 | "metadata": { 403 | "deletable": true, 404 | "editable": true, 405 | "slideshow": { 406 | "slide_type": "fragment" 407 | } 408 | }, 409 | "source": [ 410 | "- Automated tests / travis-ci " 411 | ] 412 | }, 413 | { 414 | "cell_type": "markdown", 415 | "metadata": { 416 | "deletable": true, 417 | "editable": true, 418 | "slideshow": { 419 | "slide_type": "fragment" 420 | } 421 | }, 422 | "source": [ 423 | "- An \"elm-repl\" experience" 424 | ] 425 | }, 426 | { 427 | "cell_type": "markdown", 428 | "metadata": { 429 | "deletable": true, 430 | "editable": true, 431 | "slideshow": { 432 | "slide_type": "slide" 433 | } 434 | }, 435 | "source": [ 436 | "# Get involved\n", 437 | "\n", 438 | "You can find everything at the github project page: https://github.com/abingham/jupyter-elm-kernel" 439 | ] 440 | }, 441 | { 442 | "cell_type": "markdown", 443 | "metadata": { 444 | "deletable": true, 445 | "editable": true, 446 | "slideshow": { 447 | "slide_type": "fragment" 448 | } 449 | }, 450 | "source": [ 451 | "You can help with:\n", 452 | "\n", 453 | "- Feedback about actual use" 454 | ] 455 | }, 456 | { 457 | "cell_type": "markdown", 458 | "metadata": { 459 | "deletable": true, 460 | "editable": true, 461 | "slideshow": { 462 | "slide_type": "fragment" 463 | } 464 | }, 465 | "source": [ 466 | "- Feature ideas and bug reports" 467 | ] 468 | }, 469 | { 470 | "cell_type": "markdown", 471 | "metadata": { 472 | "deletable": true, 473 | "editable": true, 474 | "slideshow": { 475 | "slide_type": "fragment" 476 | } 477 | }, 478 | "source": [ 479 | "- Pull requests" 480 | ] 481 | }, 482 | { 483 | "cell_type": "markdown", 484 | "metadata": { 485 | "deletable": true, 486 | "editable": true, 487 | "slideshow": { 488 | "slide_type": "slide" 489 | } 490 | }, 491 | "source": [ 492 | "# Thanks!\n", 493 | "\n", 494 | "----\n", 495 | "\n", 496 | " - Presentation: [github.com/abingham/jupyter-elm-kernel](https://github.com/abingham/jupyter-elm-kernel)\n", 497 | " - The Python Apprentice: [leanpub.com/python-apprentice/c/oslo-elm-day-2017](https://leanpub.com/python-apprentice/c/oslo-elm-day-2017)\n", 498 | " - Training and consulting: [sixty-north.com](http://sixty-north.com/)\n", 499 | " \n", 500 | " ----\n", 501 | " \n", 502 | "![Sixty North logo](theme/sixty-north-logo.small.png)" 503 | ] 504 | } 505 | ], 506 | "metadata": { 507 | "celltoolbar": "Slideshow", 508 | "kernelspec": { 509 | "display_name": "Python 3", 510 | "language": "python", 511 | "name": "python3" 512 | }, 513 | "language_info": { 514 | "codemirror_mode": { 515 | "name": "ipython", 516 | "version": 3 517 | }, 518 | "file_extension": ".py", 519 | "mimetype": "text/x-python", 520 | "name": "python", 521 | "nbconvert_exporter": "python", 522 | "pygments_lexer": "ipython3", 523 | "version": "3.6.0" 524 | } 525 | }, 526 | "nbformat": 4, 527 | "nbformat_minor": 2 528 | } 529 | -------------------------------------------------------------------------------- /docs/presentation/README.md: -------------------------------------------------------------------------------- 1 | To run this presentation, do this (probably in a virtualenv): 2 | 3 | ``` 4 | pip install -r requirements.txt 5 | jupyter nbextension install rise --py --sys-prefix 6 | jupyter nbextension enable rise --py --sys-prefix 7 | jupyter notebook 8 | ``` 9 | -------------------------------------------------------------------------------- /docs/presentation/images/cooper-thumbsup.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/docs/presentation/images/cooper-thumbsup.jpeg -------------------------------------------------------------------------------- /docs/presentation/images/elm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/docs/presentation/images/elm-logo.png -------------------------------------------------------------------------------- /docs/presentation/images/squarelogo-greytext-orangebody-greymoons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/docs/presentation/images/squarelogo-greytext-orangebody-greymoons.png -------------------------------------------------------------------------------- /docs/presentation/requirements.txt: -------------------------------------------------------------------------------- 1 | jupyter 2 | rise 3 | -------------------------------------------------------------------------------- /docs/presentation/theme/sixty-north-device.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/docs/presentation/theme/sixty-north-device.png -------------------------------------------------------------------------------- /docs/presentation/theme/sixty-north-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/docs/presentation/theme/sixty-north-logo.png -------------------------------------------------------------------------------- /docs/presentation/theme/sixty-north-logo.small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/docs/presentation/theme/sixty-north-logo.small.png -------------------------------------------------------------------------------- /docs/presentation/theme/sixty_north.css: -------------------------------------------------------------------------------- 1 | /** 2 | * The Sixty North reveal.js theme. 3 | * 4 | * Mostly just the "simple" theme with some modifications. 5 | */ 6 | @import url('https://fonts.googleapis.com/css?family=Inconsolata|Roboto'); 7 | /********************************************* 8 | * GLOBAL STYLES 9 | *********************************************/ 10 | body { 11 | background: white; 12 | background-color: white; 13 | font-family: "Roboto", sans-serif; 14 | font-weight: normal; 15 | } 16 | 17 | .reveal { 18 | font-family: "Roboto", sans-serif; 19 | font-size: 36px; 20 | font-weight: normal; 21 | letter-spacing: -0.02em; 22 | color: black; } 23 | 24 | /* .rise-enabled { */ 25 | /* background-color: white !important; */ 26 | /* font-size: 125% !important; */ 27 | /* background-image: url(sixty-north-device.png); */ 28 | /* background-position: 99% 2%; */ 29 | /* background-repeat: no-repeat; */ 30 | /* background-origin: content-box; */ 31 | /* padding: 5px 5px 5px 5px; */ 32 | /* } */ 33 | 34 | 35 | ::selection { 36 | color: white; 37 | background: rgba(0, 0, 0, 0.99); 38 | text-shadow: none; } 39 | 40 | /********************************************* 41 | * HEADERS 42 | *********************************************/ 43 | .reveal h1, 44 | .reveal h2, 45 | .reveal h3, 46 | .reveal h4, 47 | .reveal h5, 48 | .reveal h6 { 49 | margin: 0 0 20px 0; 50 | color: black; 51 | line-height: 0.9em; 52 | letter-spacing: 0.02em; 53 | text-transform: none; 54 | text-shadow: none; } 55 | 56 | h2 { 57 | font-weight: 100; 58 | color: #888888; 59 | } 60 | 61 | .reveal h2 { 62 | font-weight: 100; 63 | color: #888888; 64 | } 65 | 66 | /********************************************* 67 | * LINKS 68 | *********************************************/ 69 | .reveal a:not(.image) { 70 | color: darkblue; 71 | text-decoration: none; 72 | -webkit-transition: color .15s ease; 73 | -moz-transition: color .15s ease; 74 | -ms-transition: color .15s ease; 75 | -o-transition: color .15s ease; 76 | transition: color .15s ease; } 77 | 78 | .reveal a:not(.image):hover { 79 | color: #0000f1; 80 | text-shadow: none; 81 | border: none; } 82 | 83 | .reveal .roll span:after { 84 | color: #fff; 85 | background: #00003f; } 86 | 87 | /********************************************* 88 | * IMAGES 89 | *********************************************/ 90 | .reveal section img { 91 | margin: 15px 0px; 92 | background: rgba(255, 255, 255, 0.12); 93 | border: 4px solid black; 94 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); 95 | -webkit-transition: all .2s linear; 96 | -moz-transition: all .2s linear; 97 | -ms-transition: all .2s linear; 98 | -o-transition: all .2s linear; 99 | transition: all .2s linear; } 100 | 101 | .reveal a:hover img { 102 | background: rgba(255, 255, 255, 0.2); 103 | border-color: darkblue; 104 | box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); } 105 | 106 | /********************************************* 107 | * NAVIGATION CONTROLS 108 | *********************************************/ 109 | .reveal .controls div.navigate-left, 110 | .reveal .controls div.navigate-left.enabled { 111 | border-right-color: darkblue; } 112 | 113 | .reveal .controls div.navigate-right, 114 | .reveal .controls div.navigate-right.enabled { 115 | border-left-color: darkblue; } 116 | 117 | .reveal .controls div.navigate-up, 118 | .reveal .controls div.navigate-up.enabled { 119 | border-bottom-color: darkblue; } 120 | 121 | .reveal .controls div.navigate-down, 122 | .reveal .controls div.navigate-down.enabled { 123 | border-top-color: darkblue; } 124 | 125 | .reveal .controls div.navigate-left.enabled:hover { 126 | border-right-color: #0000f1; } 127 | 128 | .reveal .controls div.navigate-right.enabled:hover { 129 | border-left-color: #0000f1; } 130 | 131 | .reveal .controls div.navigate-up.enabled:hover { 132 | border-bottom-color: #0000f1; } 133 | 134 | .reveal .controls div.navigate-down.enabled:hover { 135 | border-top-color: #0000f1; } 136 | 137 | /********************************************* 138 | * PROGRESS BAR 139 | *********************************************/ 140 | .reveal .progress { 141 | background: rgba(0, 0, 0, 0.2); } 142 | 143 | .reveal .progress span { 144 | background: darkblue; 145 | -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 146 | -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 147 | -ms-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 148 | -o-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); 149 | transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } 150 | 151 | /********************************************* 152 | * SLIDE NUMBER 153 | *********************************************/ 154 | .reveal .slide-number { 155 | color: darkblue; } 156 | 157 | .reveal section img { 158 | border: 0px solid black; 159 | box-shadow: none; } 160 | 161 | .reveal h1, 162 | .reveal h2, 163 | .reveal h3, 164 | .reveal h4, 165 | .reveal h5, 166 | .reveal h6 { 167 | word-wrap: normal; 168 | -webkit-hyphens: manual; 169 | -moz-hyphens: manual; 170 | hyphens: manual; } 171 | 172 | pre.src { 173 | background-color: #020202; 174 | color: #dddddd; 175 | } 176 | 177 | code { 178 | font-family: "Inconsolata", sans-serif; 179 | line-height: 1.4em; 180 | } 181 | 182 | .reveal .text_cell { 183 | font-size: 24px; 184 | } 185 | 186 | .reveal .code_cell { 187 | font-size: 24px; 188 | } 189 | 190 | .example { 191 | background-color: #FFFFCC; } 192 | -------------------------------------------------------------------------------- /elm_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | """A Jupyter kernel for Elm""" 2 | 3 | __version__ = '0.21.1' 4 | 5 | from .kernel import ElmKernel 6 | -------------------------------------------------------------------------------- /elm_kernel/__main__.py: -------------------------------------------------------------------------------- 1 | from ipykernel.kernelapp import IPKernelApp 2 | from . import ElmKernel 3 | 4 | IPKernelApp.launch_instance(kernel_class=ElmKernel) 5 | -------------------------------------------------------------------------------- /elm_kernel/install.py: -------------------------------------------------------------------------------- 1 | # Copied from the echo_kernel example: 2 | # https://github.com/jupyter/echo_kernel/blob/master/echo_kernel/install.py 3 | #!/usr/bin/python3 4 | import argparse 5 | import json 6 | import os 7 | import sys 8 | 9 | from jupyter_client.kernelspec import KernelSpecManager 10 | from IPython.utils.tempdir import TemporaryDirectory 11 | 12 | kernel_json = { 13 | "argv": [sys.executable, "-m", "elm_kernel", "-f", "{connection_file}"], 14 | "display_name": "Elm", 15 | "language": "elm", 16 | } 17 | 18 | 19 | def install_my_kernel_spec(user=True, prefix=None): 20 | with TemporaryDirectory() as td: 21 | os.chmod(td, 0o755) # Starts off as 700, not user readable 22 | with open(os.path.join(td, 'kernel.json'), 'w') as f: 23 | json.dump(kernel_json, f, sort_keys=True) 24 | 25 | print('Installing Jupyter kernel spec') 26 | ksm = KernelSpecManager() 27 | ksm.install_kernel_spec( 28 | td, 'elm', user=user, prefix=prefix) 29 | 30 | install_dir = ksm.get_kernel_spec(kernel_name="elm").resource_dir 31 | 32 | print("Installed to ", install_dir) 33 | 34 | 35 | def _is_root(): 36 | try: 37 | return os.geteuid() == 0 38 | except AttributeError: 39 | return False # assume not an admin on non-Unix platforms 40 | 41 | 42 | def main(argv=None): 43 | ap = argparse.ArgumentParser() 44 | ap.add_argument( 45 | '--user', action='store_true', 46 | help="Install to the per-user kernels registry. Default if not root.") 47 | ap.add_argument( 48 | '--sys-prefix', action='store_true', 49 | help="Install to sys.prefix (e.g. a virtualenv or conda env)") 50 | ap.add_argument( 51 | '--prefix', 52 | help="Install to the given prefix. " 53 | "Kernelspec will be installed in {PREFIX}/share/jupyter/kernels/") 54 | args = ap.parse_args(argv) 55 | 56 | if args.sys_prefix: 57 | args.prefix = sys.prefix 58 | if not args.prefix and not _is_root(): 59 | args.user = True 60 | 61 | if args.user: 62 | print("Elm Kernel will be Installed to per-user jupyter kernel registry.") 63 | else: 64 | print("Elm Kernel will be Installed to global jupyter kernel registry.") 65 | 66 | install_my_kernel_spec(user=args.user, prefix=args.prefix) 67 | 68 | 69 | if __name__ == '__main__': 70 | main(sys.argv[1:]) 71 | -------------------------------------------------------------------------------- /elm_kernel/kernel.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | import contextlib 3 | import io 4 | from ipykernel.kernelbase import Kernel 5 | import os 6 | import shutil 7 | import subprocess 8 | import sys 9 | from tempfile import TemporaryDirectory 10 | 11 | 12 | class ElmKernel(Kernel): 13 | implementation = 'elm_kernel' 14 | implementation_version = '0.21.1' 15 | language = 'no-op' 16 | language_version = '0.19.1' 17 | language_info = {'name': 'elm', 18 | 'codemirror_mode': 'elm', 19 | 'mimetype': 'text/x-elm', 20 | 'file_extension': '.elm'} 21 | banner = "Display Elm output" 22 | 23 | def __init__(self, *args, **kwargs): 24 | super().__init__(*args, **kwargs) 25 | self._code = [] 26 | self._tempdir = TemporaryDirectory() 27 | 28 | 29 | def do_shutdown(self, restart): 30 | self._tempdir.cleanup() 31 | 32 | def do_execute(self, code, silent, 33 | store_history=True, 34 | user_expressions=None, 35 | allow_stdin=False): 36 | self._code.append(code) 37 | 38 | if self._should_compile(): 39 | try: 40 | code = "\n".join(self._code) 41 | self._code = [] 42 | self._compile(code) 43 | except Exception as exc: 44 | 45 | self._send_error_result(str(exc)) 46 | return { 47 | 'status': 'error', 48 | 'execution_count': self.execution_count, 49 | } 50 | 51 | return { 52 | 'status': 'ok', 53 | 'execution_count': self.execution_count, 54 | 'payload': [], 55 | 'user_expressions': {}, 56 | } 57 | 58 | @contextlib.contextmanager 59 | def _temp_path(self, filename): 60 | """Yield `filename` inside the tempdir, but don't actually create the file. 61 | Then, on exit, delete the file if it exists. 62 | """ 63 | try: 64 | path = os.path.join(self._tempdir.name, filename) 65 | yield path 66 | finally: 67 | with contextlib.suppress(OSError): 68 | if os.path.isfile(path) or os.path.islink(path): 69 | os.remove(path) 70 | else: 71 | shutil.rmtree(path, ignore_errors=True) 72 | 73 | 74 | def _elm_init(self): 75 | ''' Generate an init file in the temporary directory 76 | ''' 77 | proc = subprocess.Popen( 78 | ['elm', 'init'], 79 | cwd=self._tempdir.name, 80 | stdin=subprocess.PIPE, 81 | stderr=subprocess.STDOUT, 82 | encoding=sys.getdefaultencoding()) 83 | 84 | # respond to prompt in 'elm init' 85 | proc.communicate(input="y\n") 86 | proc.wait() 87 | 88 | def _elm_make(self, infile, outfile): 89 | 90 | subprocess.run( 91 | ['elm', 'make', 92 | infile, '--output={}'.format(outfile)], 93 | check=True, 94 | stdout=subprocess.PIPE, 95 | stderr=subprocess.STDOUT, 96 | cwd=self._tempdir.name, 97 | encoding=sys.getdefaultencoding()) 98 | 99 | 100 | def _compile(self, code): 101 | self._link_build_environment() 102 | 103 | with self._temp_path('input.elm') as infile,\ 104 | self._temp_path('index.js') as outfile,\ 105 | self._temp_path('elm.json') as elm_json,\ 106 | self._temp_path('src') as src_path: 107 | 108 | with open(infile, mode='wt') as f: 109 | 110 | f.write(code) 111 | 112 | try: 113 | # if elm.json doesn't exist yet, create it 114 | if not os.path.lexists(elm_json): 115 | self._elm_init() 116 | if not os.path.lexists(src_path): 117 | os.mkdir(src_path) 118 | 119 | self._elm_make(infile, outfile) 120 | 121 | with open(outfile, mode='rt') as f: 122 | javascript = f.read() 123 | 124 | self._send_success_result(javascript) 125 | 126 | except subprocess.CalledProcessError as err: 127 | # When compilation fails we send the compiler output to the 128 | # user but we don't count this as an error. A compiler error 129 | # might actually be the desired output of the cell. 130 | self._send_error_result(err.stdout) 131 | 132 | except Exception as err: 133 | self._send_error_result(repr(err)) 134 | raise 135 | 136 | 137 | def _should_compile(self): 138 | assert self._code, "Should not be querying for compilation with no code!" 139 | lines = deque(io.StringIO(self._code[-1]), 1) 140 | return lines[0] == '-- compile-code' if lines else False 141 | 142 | def _send_error_result(self, msg): 143 | """Send an error message to the client. 144 | 145 | `msg` is the message to be sent to the client. 146 | """ 147 | self.send_response( 148 | self.iopub_socket, 149 | 'display_data', 150 | { 151 | 'metadata': {}, 152 | 'data': { 153 | 'text/html': '
{}
'.format(msg) 154 | } 155 | } 156 | ) 157 | 158 | def _send_success_result(self, javascript): 159 | """Send messages to the client with the results of a successful compilation. 160 | 161 | `javascript` is the javascript generated by elm-make. 162 | """ 163 | # TODO: pull module name from `code` 164 | module_name = "Main" 165 | 166 | div_id = 'elm-div-' + str(self.execution_count) 167 | 168 | template = """ 169 | var defineElm = function(cb) {{ 170 | if (this.Elm) {{ 171 | this.oldElm = this.Elm; 172 | }} 173 | var define = null; 174 | 175 | {js} 176 | 177 | cb(); 178 | }} 179 | ; 180 | 181 | var obj = new Object(); 182 | defineElm.bind(obj)(function(){{ 183 | var mountNode = document.getElementById('{div_id}'); 184 | obj.Elm. {module_name}.init({{ node: mountNode }}); 185 | }}); 186 | """ 187 | 188 | javascript = template.format( 189 | js=javascript, 190 | module_name=module_name, 191 | div_id=div_id) 192 | 193 | self.send_response( 194 | self.iopub_socket, 195 | 'display_data', 196 | { 197 | 'metadata': {}, 198 | 'data': { 199 | 'text/html': '
' 200 | } 201 | } 202 | ) 203 | 204 | self.send_response( 205 | self.iopub_socket, 206 | 'display_data', 207 | { 208 | 'metadata': {}, 209 | 'data': { 210 | 'application/javascript': javascript 211 | } 212 | }) 213 | 214 | def _link_build_environment(self): 215 | """Link elm.json and src to temporary directory where elm code is compiled 216 | """ 217 | # existence of elm.json is not mandatory 218 | if os.path.isfile('elm.json'): 219 | # symlink requires abolute path 220 | os.symlink( 221 | os.path.join(os.getcwd(), 'elm.json'), 222 | os.path.join(self._tempdir.name, 'elm.json')) 223 | 224 | if os.path.isdir('src'): 225 | os.symlink( 226 | os.path.join(os.getcwd(), 'src'), 227 | os.path.join(self._tempdir.name, 'src')) 228 | 229 | 230 | if __name__ == '__main__': 231 | from ipykernel.kernelapp import IPKernelApp 232 | IPKernelApp.launch_instance(kernel_class=ElmKernel) 233 | -------------------------------------------------------------------------------- /examples/hello-world.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Hello, world!\n", 8 | "This is the obligatory \"hello world\" for the Elm Jupyter kernel." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import Html\n", 18 | "\n", 19 | "main = \n", 20 | " Html.text \"Hello, world!\"\n", 21 | " \n", 22 | "-- compile-code" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [] 31 | } 32 | ], 33 | "metadata": { 34 | "kernelspec": { 35 | "display_name": "Elm", 36 | "language": "elm", 37 | "name": "elm" 38 | }, 39 | "language_info": { 40 | "codemirror_mode": "elm", 41 | "file_extension": ".elm", 42 | "mimetype": "text/x-elm", 43 | "name": "elm" 44 | } 45 | }, 46 | "nbformat": 4, 47 | "nbformat_minor": 2 48 | } 49 | -------------------------------------------------------------------------------- /examples/images/tea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abingham/jupyter-elm-kernel/2973205caec5b68d0bf9eac87986063ffcdf3f77/examples/images/tea.png -------------------------------------------------------------------------------- /examples/svg/SVG example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Rendering SVGs in a notebook\n", 8 | "This shows how you can draw SVGs in notebooks using the `elm/svg` module.\n", 9 | "\n", 10 | "\n", 11 | "To do this, you need to make sure you have an `elm.json` in the same directory as the notebook. The `elm.json` needs to have a dependency on `elm/svg`, something like this:" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "```json\n", 19 | "{\n", 20 | " \"type\": \"application\",\n", 21 | " \"source-directories\": [],\n", 22 | " \"elm-version\": \"0.19.0\",\n", 23 | " \"dependencies\": {\n", 24 | " \"direct\": {\n", 25 | " \"elm/browser\": \"1.0.1\",\n", 26 | " \"elm/core\": \"1.0.2\",\n", 27 | " \"elm/html\": \"1.0.0\",\n", 28 | " \"elm/svg\": \"1.0.1\"\n", 29 | " },\n", 30 | " \"indirect\": {\n", 31 | " \"elm/json\": \"1.1.3\",\n", 32 | " \"elm/time\": \"1.0.0\",\n", 33 | " \"elm/url\": \"1.0.0\",\n", 34 | " \"elm/virtual-dom\": \"1.0.2\"\n", 35 | " }\n", 36 | " },\n", 37 | " \"test-dependencies\": {\n", 38 | " \"direct\": {},\n", 39 | " \"indirect\": {}\n", 40 | " }\n", 41 | "}\n", 42 | "```" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "## Basic SVG example" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "scrolled": false 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "import Html\n", 61 | "import Svg exposing (..)\n", 62 | "import Svg.Attributes exposing (..)\n", 63 | "\n", 64 | "main = \n", 65 | " svg [stroke \"#f00\"] [line [x1 \"0\", y1 \"1\", x2 \"100\", y2 \"100\"] []]\n", 66 | " \n", 67 | "-- compile-code" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "## A more complex example" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": null, 80 | "metadata": { 81 | "scrolled": false 82 | }, 83 | "outputs": [], 84 | "source": [ 85 | "import Svg exposing (..)\n", 86 | "import Svg.Attributes exposing (..)\n", 87 | "\n", 88 | "main =\n", 89 | " svg\n", 90 | " [ version \"1.1\", x \"0\", y \"0\", viewBox \"0 0 323.141 322.95\", height \"200px\", width \"200px\"\n", 91 | " ]\n", 92 | " [ polygon [ fill \"#F0AD00\", points \"161.649,152.782 231.514,82.916 91.783,82.916\" ] []\n", 93 | " , polygon [ fill \"#7FD13B\", points \"8.867,0 79.241,70.375 232.213,70.375 161.838,0\" ] []\n", 94 | " , rect\n", 95 | " [ fill \"#7FD13B\", x \"192.99\", y \"107.392\", width \"107.676\", height \"108.167\"\n", 96 | " , transform \"matrix(0.7071 0.7071 -0.7071 0.7071 186.4727 -127.2386)\"\n", 97 | " ]\n", 98 | " []\n", 99 | " , polygon [ fill \"#60B5CC\", points \"323.298,143.724 323.298,0 179.573,0\" ] []\n", 100 | " , polygon [ fill \"#5A6378\", points \"152.781,161.649 0,8.868 0,314.432\" ] []\n", 101 | " , polygon [ fill \"#F0AD00\", points \"255.522,246.655 323.298,314.432 323.298,178.879\" ] []\n", 102 | " , polygon [ fill \"#60B5CC\", points \"161.649,170.517 8.869,323.298 314.43,323.298\" ] []\n", 103 | " ]\n", 104 | " \n", 105 | "-- compile-code" 106 | ] 107 | } 108 | ], 109 | "metadata": { 110 | "kernelspec": { 111 | "display_name": "Elm", 112 | "language": "elm", 113 | "name": "elm" 114 | }, 115 | "language_info": { 116 | "codemirror_mode": "elm", 117 | "file_extension": ".elm", 118 | "mimetype": "text/x-elm", 119 | "name": "elm" 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 2 124 | } 125 | -------------------------------------------------------------------------------- /examples/svg/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": ["src"], 4 | "elm-version": "0.19.1", 5 | "dependencies": { 6 | "direct": { 7 | "elm/browser": "1.0.1", 8 | "elm/core": "1.0.2", 9 | "elm/html": "1.0.0", 10 | "elm/svg": "1.0.1" 11 | }, 12 | "indirect": { 13 | "elm/json": "1.1.3", 14 | "elm/time": "1.0.0", 15 | "elm/url": "1.0.0", 16 | "elm/virtual-dom": "1.0.2" 17 | } 18 | }, 19 | "test-dependencies": { 20 | "direct": {}, 21 | "indirect": {} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/the-elm-architecture.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# The Elm Architecture\n", 8 | "\n", 9 | "This is a pretty minimal example of The Elm Architecture in a Jupyter notebook.\n", 10 | "\n", 11 | "![The Elm architecture](images/tea.png)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## Imports\n", 19 | "First, we have to import a few other modules" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import Browser\n", 29 | "import Html exposing (..)\n", 30 | "import Html.Events exposing (..)" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## Messages\n", 38 | "Next we define our message \"vocabulary\". These are the various ways in which our model can be updated." 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "type Msg = Inc | Dec" 48 | ] 49 | }, 50 | { 51 | "cell_type": "markdown", 52 | "metadata": {}, 53 | "source": [ 54 | "## Model\n", 55 | "Our model is simply an integer in this case. While it's not strictly necessary to create an alias for it, we'll include one for completeness; for more complex models you'll almost always have an alias." 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [ 64 | "type alias Model = Int" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "## Init and subscriptions\n", 72 | "Next we define our init and subscriptions. Init passes the starting value in to our main program.\n", 73 | "Subscriptions is for telling our main program what events we want to receive." 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "init : () -> (Model, Cmd Msg)\n", 83 | "init _ = (0, Cmd.none)\n", 84 | "\n", 85 | "subscriptions : Model -> Sub Msg\n", 86 | "subscriptions _ = Sub.none" 87 | ] 88 | }, 89 | { 90 | "cell_type": "markdown", 91 | "metadata": {}, 92 | "source": [ 93 | "## View\n", 94 | "The `view` function renders our model." 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "view : Model -> Html Msg\n", 104 | "view model =\n", 105 | " div []\n", 106 | " [ button [ onClick Inc ] [ text \"+\" ]\n", 107 | " , div [] [ text (String.fromInt model) ]\n", 108 | " , button [ onClick Dec ] [ text \"-\" ]\n", 109 | " ]" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "## Update\n", 117 | "The `update` function takes a `Msg` and a `Model`, returning a new, updated `Model`. " 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "execution_count": null, 123 | "metadata": {}, 124 | "outputs": [], 125 | "source": [ 126 | "update : Msg -> Model -> (Model, Cmd Msg)\n", 127 | "update msg model =\n", 128 | " case msg of\n", 129 | " Inc -> (model + 1, Cmd.none)\n", 130 | " Dec -> (model - 1, Cmd.none)" 131 | ] 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "metadata": {}, 136 | "source": [ 137 | "## Main\n", 138 | "Finally we tie everything together with `main`. " 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "main =\n", 148 | " Browser.element \n", 149 | " { init = init\n", 150 | " , view = view\n", 151 | " , update = update\n", 152 | " , subscriptions = subscriptions\n", 153 | " }\n", 154 | " \n", 155 | "-- compile-code" 156 | ] 157 | } 158 | ], 159 | "metadata": { 160 | "kernelspec": { 161 | "display_name": "Elm", 162 | "language": "elm", 163 | "name": "elm" 164 | }, 165 | "language_info": { 166 | "codemirror_mode": "elm", 167 | "file_extension": ".elm", 168 | "mimetype": "text/x-elm", 169 | "name": "elm" 170 | } 171 | }, 172 | "nbformat": 4, 173 | "nbformat_minor": 2 174 | } 175 | -------------------------------------------------------------------------------- /kernel.json: -------------------------------------------------------------------------------- 1 | {"argv":["python3","-m","elm_kernel", "-f", "{connection_file}"], 2 | "display_name":"Elm" 3 | } 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open('README.md') as f: 4 | readme = f.read() 5 | 6 | setup( 7 | name='elm_kernel', 8 | version='0.21.1', 9 | packages=['elm_kernel'], 10 | description='Jupyter kernel for executing Elm code', 11 | long_description=readme, 12 | author='Austin Bingham', 13 | author_email='austin@sixty-north.com', 14 | url='https://github.com/abingham/jupyter-elm-kernel', 15 | classifiers=[ 16 | 'Intended Audience :: Developers', 17 | 'License :: OSI Approved :: MIT License', 18 | 'Programming Language :: Python :: 3', 19 | ], 20 | install_requires=['jupyter'], 21 | ) 22 | --------------------------------------------------------------------------------