├── .dockerignore ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── app.js ├── archive ├── kitchen-sink │ ├── fig1.jpg │ ├── ice-cream-sales.xml │ ├── manifest.xml │ └── manuscript.xml ├── py-jupyter │ ├── bibliography.bibtex │ ├── manifest.xml │ ├── py-jupyter.ipynb │ └── py-jupyter.ipynb.jats.xml └── r-markdown │ ├── bibliography.bibtex │ ├── manifest.xml │ ├── rmarkdown.Rmd │ └── rmarkdown.Rmd.jats.xml ├── index.html ├── nbstencilaproxy ├── __init__.py ├── handlers.py ├── package.json └── static │ └── tree.js ├── new-session-button.png ├── package-lock.json ├── package.json ├── setup.py ├── stencila-host.js └── stencila.js /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | node_modules 3 | stencila 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # general 86 | .DS_Store 87 | .cache 88 | .pytest-cache 89 | nbstencilaproxy/package-lock.json 90 | 91 | # virtualenv 92 | .venv 93 | venv/ 94 | ENV/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | 109 | # javascript 110 | node_modules 111 | 112 | \.vscode/ 113 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at benjaminrk@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Min RK 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | prune nbstencilaproxy/node_modules 2 | include nbstencilaproxy/package.json 3 | include *.html 4 | include *.md 5 | include *.js 6 | include *.json 7 | include *ignore 8 | graft archive 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jupyter + Stencila = `nbstencilaproxy` 2 | 3 | [Jupyter](https://jupyter.org/) + [Dar](https://github.com/substance/dar) file format compatibility exploration for running [Stencila](http://stenci.la/) editor on [Binder](https://mybinder.org/) 4 | 5 | [![PyPI version](https://badge.fury.io/py/nbstencilaproxy.svg)](https://badge.fury.io/py/nbstencilaproxy) 6 | 7 | ## Demo 8 | 9 | Click on the button below to launch an online Jupyter instance on [mybinder.org](https://mybinder.org) based on this repository: 10 | 11 | [![Binder](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/minrk/nbstencilaproxy/master?urlpath=stencila) 12 | 13 | Open an the example Dar project by clicking on "New > Stencila Session": 14 | 15 | ![](new-session-button.png) 16 | 17 | ## About 18 | 19 | This project is part of the [eLife Innovation Sprint 2018](https://elifesci.org/innovationsprint2018) and [Mozilla Global Sprint 2018](https://mozilla.github.io/global-sprint/) (see [https://github.com/mozilla/global-sprint/issues/317](https://github.com/mozilla/global-sprint/issues/317)) 20 | 21 | The projects core module is a Python package, `nbstencilaproxy`, which is [available on PyPI](https://pypi.org/project/nbstencilaproxy/). 22 | It is based on [`nbserverproxy`](https://github.com/jupyterhub/nbserverproxy) to run the services and the UI required to edit Stencila documents in the browser. 23 | The concept is inspired by [`nbrsessionproxy`](https://github.com/jupyterhub/nbrsessionproxy). 24 | 25 | `nbstencilaproxy` includes the following components: 26 | 27 | - a JavaScript package for installing and running Stencila (client/UI and services) in a Jupyter container 28 | - extension for the Jupyter UI to add a "New Stencila Session" button 29 | 30 | ## Team 31 | 32 | - [@minrk](https://github.com/minrk) 33 | - [@nuest](https://github.com/nuest) 34 | 35 | ## How does it work? 36 | 37 | ### The building blocks 38 | 39 | Binder relies on [`repo2docker`](https://repo2docker.readthedocs.io/en/latest/) create a runtime environment from source code repositories. 40 | `repo2docker` was extended as part of this project to detect Dar documents using the file `manifest.xml`, which is contained in all Dars. 41 | If a `manifest.xml` is found, then `repo2docker` installs `nbstencilaproxy` into the image (see [code](https://github.com/jupyter/repo2docker/blob/master/repo2docker/buildpacks/base.py#L521)) and creates environment variables (`STENCILA_ARCHIVE_DIR` and `STENCILA_ARCHIVE`) to configure a single Dar document to be viewed in the Stencila UI. 42 | 43 | The installation consists of the following steps and components: 44 | 45 | 1. Install [Stencila for Python](https://github.com/stencila/py) from GitHub and register the package (thanks to the registration, the Stencila Host can find and connect these runtimes) 46 | 1. Install `nbstencilaproxy` itself, including a JavaScript module (see below for description of the npm; see `setup.py` for installation of the npm package as part of the Python module) 47 | 1. Install and enable the `nbstencilaproxy` **Jupyter notebook server extension**, which serves the Stencila UI and services via proxies (see `nbstencilaproxy/handlers.py`) 48 | 1. Install and enable the `nbstencilaproxy` **Jupyter notebook extension**, which adds a menu item into the Jupyter UI to open Stencila (see `nbstencilaproxy/static/tree.js`) 49 | 50 | ### JavaScript module within `nbstencilaproxy` 51 | 52 | The PyPI module includes a small [npm package](https://www.npmjs.com/) with JavaScript code, which serves the following purposes: 53 | 54 | - pulls in Stencila' JavaScript depenencies, namely `stencila` and `stencila-host` (see `package.json`) 55 | - includes code to run the `dar-server` and a static file server for the app using the files from the the module `stencila`s directory `dist` (see the file `stencila.js` for details) 56 | - run a `stencila-host` on the port provided by nbserverproxy (see `stencila-host.js`) 57 | 58 | This gives us control of the paths and let's us get rid of complex development features (e.g. `substance-bundler`). 59 | 60 | The package includes [`stencila-node`](https://www.npmjs.com/package/stencila-node) as a dependency. 61 | It provides the `JupyterContext` as well as a `NodeContext` (for executing Javascript) and a `SqliteContext` (for executing SQL). 62 | 63 | We also made our own version of `app.js`, getting rid of the virtual file storage stuff (`vfs`), defaulting storage to `fs` (file system), because that is what is needed for Jupyter - we do not need to host any examples. 64 | In the same line, we built own `index.html` (based on `example.html`) and serve that, which allows us to directly render a Dar document instead of a listing of examples and instruction and to use `app.js`. 65 | 66 | Relevant _path configurations_ comprise (a) the local storage path and (b) the URLs used by the client, accessing the `dar-server` through `nbserverproxy`. 67 | 68 | ### Connecting Stencila to Jupyter kernels 69 | 70 | Stencila has ["execution contexts"](https://stenci.la/learn/intro.html) (the equivalent of Jupyter's "kernels") for R, Python, SQL, JavaScript (Node.js), and Mini (Stencila's own simple language). 71 | Execution contexts differ from kernels in a number of ways including local execution and dependency analysis of cells. 72 | Both of these are necessary for the reactive, functional execution model of Stencila Articles and Sheets. 73 | 74 | It is possible to install these execution contexts in the Docker image, and is done so for R 75 | However, Stencila also has a `JupyterContext` which acts as a bridge between Stencila's API and Jupyter kernels. 76 | Since the base image for BinderHub already has a Jupyter kernel for Python installed, it can always be used. 77 | This does mean however, that some of the reactive aspects of the Stencila UI won't work as expected. 78 | Also the `JupyterContext` is not well developed or tested. 79 | 80 | ## Install locally 81 | 82 | If you have a local Jupyter instance, you can install the package directly: 83 | 84 | ``` 85 | pip install nbstencilaproxy 86 | ``` 87 | 88 | Enable the extensions for all users on the system: 89 | 90 | ``` 91 | jupyter serverextension enable --py --sys-prefix nbstencilaproxy 92 | jupyter nbextension install --py --sys-prefix nbstencilaproxy 93 | jupyter nbextension enable --py --sys-prefix nbstencilaproxy 94 | ``` 95 | 96 | ## Run locally with `repo2docker` 97 | 98 | You can run the latest release of `nbstencilaproxy` as part of `repo2docker`. 99 | 100 | ```bash 101 | # install repo2docker: https://repo2docker.readthedocs.io/en/latest/usage.html#running-repo2docker-locally 102 | 103 | # run repo2docker for a repository with Dar directories, e.g. archive/ in this repository (add '--no-build' option to only inspect Dockerfile) 104 | repo2docker --debug ./archive 105 | ``` 106 | 107 | - Login by visiting the tokenized URL displayed e.g. `http://localhost:8888/?token=99a7bc13...` 108 | - Click on the "New > Stencila Session" button on the Jupyter start page, opening the `py-jupyter` example, or 109 | - Open one of the included examples by appending the following parameters to the URL: 110 | - Python (Jupyter Kernel): `?archive=py-jupyter` 111 | - R: `?archive=r-markdown` 112 | - Mini ([Stencila's own data analysis language](https://github.com/stencila/mini)): `?archive=kitchen-sink` 113 | 114 | ## Development 115 | 116 | ### Updating Stencila 117 | 118 | The following places use/install Stencila components in specific versions: 119 | 120 | - `repo2docker/buildpacks/base.py` installs github.com/stencila/py 121 | - `repo2docker/buildpacks/r.py` installs github.com/stencila/r 122 | - `nbstencilaproxy/package.json` installs [`stencila-node`](https://www.npmjs.com/package/stencila-node) and [`stencila`](https://www.npmjs.com/package/stencila) from npm 123 | 124 | ### Using local `nbstencilaproxy` in `repo2docker` 125 | 126 | This is quite tricky to do, but useful to test the whole stack of tools. 127 | It could be possible to copy the local files into the container, but a more practical approach is to temporarily telling `repo2docker` to install `nbstencilaproxy` from ones own fork. 128 | 129 | In `repo2docker/buildpacks/base.py` find the line with 130 | 131 | ```bash 132 | ${NB_PYTHON_PREFIX}/bin/pip install --no-cache nbstencilaproxy==0.1.1 && \ 133 | ``` 134 | 135 | which installs `nbstencilaproxy` from PyPI. 136 | Replace it with the following line (using your username for ``) 137 | 138 | ```bash 139 | ${NB_PYTHON_PREFIX}/bin/pip install https://github.com//nbstencilaproxy/archive/master.tar.gz && \ 140 | ``` 141 | 142 | ## License 143 | 144 | BSD 3-Clause License 145 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('substance'), require('substance-texture'), require('stencila')) : 3 | typeof define === 'function' && define.amd ? define(['substance', 'substance-texture', 'stencila'], factory) : 4 | (factory(global.window.substance,global.window.texture,global.window.stencila)); 5 | }(this, (function (substance,substanceTexture,stencila) { 'use strict'; 6 | 7 | window.addEventListener('load', () => { 8 | substance.substanceGlobals.DEBUG_RENDERING = substance.platform.devtools; 9 | stencila.StencilaWebApp.mount({ 10 | archiveId: substance.getQueryStringParam('archive') || '{{archive}}', 11 | storageType: substance.getQueryStringParam('storage') || 'fs', 12 | storageUrl: substance.getQueryStringParam('storageUrl') || './archives' 13 | }, window.document.body); 14 | }); 15 | 16 | }))); 17 | -------------------------------------------------------------------------------- /archive/kitchen-sink/fig1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minrk/nbstencilaproxy/74adbb3669f6ee7ccca1c54cc77fd882eae800ae/archive/kitchen-sink/fig1.jpg -------------------------------------------------------------------------------- /archive/kitchen-sink/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /archive/kitchen-sink/manuscript.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | Reproducible Document Stack – supporting the next-generation research article 8 | 9 | 10 | 11 | 12 | Bentley 13 | Nokome 14 | 15 | 16 | 17 | 18 | 19 | Buchtala 20 | Oliver 21 | 22 | 23 | 24 | 25 | 26 | 27 | Aufreiter 28 | Michael 29 | 30 | 31 | 32 | 33 | 34 | 35 | Maciocci 36 | Giuliano 37 | 38 | 39 | 40 | 41 | 42 | Penfold 43 | Naomi 44 | 45 | 46 | 47 | 48 | 49 | Stencila Ltd. 50 | Kaikoura 51 | New Zealand 52 | 53 | 54 | Substance Software GmbH 55 | Linz 56 | Austria 57 | 58 | 59 | eLife 60 | Cambridge 61 | UK 62 | 63 | 64 | 65 |

A reproducible document is one where any code used by the original researcher to compute an output (a graph, equation, table or statistical result) is embedded within the narrative describing the work. [1]

66 |
67 | 68 |

Deutsche Übersetzung

69 |
70 |
71 |
72 | 73 | 74 | Cells 75 |

Cells are the excutable (dynamic) bits of a document [2]. They produce a result as you type. Try to modify the following cell.

76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |

Cells use a very simple expression language called Mini to allow you to perform common data analysis and visualisation tasks within the document.

85 |

You can edit this document just like in a traditional word processor. Press enter to start a new paragraph or insert a table or cell.

86 | 87 | 88 | 89 | Behavioural design and implantation details. 90 |

Lorem ipsum dolor sit amet, ea ludus intellegat his, graece fastidii phaedrum ea mea, ne duo esse omnium. Ne mel quas quodsi. Ea pri semper nostrum necessitatibus. Mea salutatus gloriatur persecuti eu, augue oportere efficiendi at eum. Ex eam mutat falli dicit.

91 | 92 | 93 |
94 |
95 | 96 | Functions 97 |

Here's a cell that reads some fictitous ice cream sales data from a CSV (comma separated values) string using the csv function.

98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |

The variable data is a table and is now a part of of the document scope - the set of variables the document knows about. Let's filter the table to only show data for days when the temperature is less than 20 degrees.

107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |

Notice that because we didn't assign the result of filter to a value the output gets displayed. You can change this behaviour by setting the cell's output visibility to Hide.

116 |

Cells can also be used to plot your data. The plot function is a generic function that allows you to visualize data using various visual encodings.

117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 | Polyglot programming 128 |

You can mix programming languages. Here we use SQL to filter a table

129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |

The variable data is a table and is now a part of of the document scope - the set of variables the document knows about. Let's filter the table to only show data for days when the temperature is less than 20 degrees.

138 | 139 | 140 | 141 | Ice Cream Sales on Sunny vs. Cloudy Days 142 |

This is a reproducible figure, including a chart implented in R using the ggplot function.

143 | 144 | 145 | 148 | 149 | 150 |
151 |
152 | 153 | 154 | 155 | 156 |

Lorem ipsum dolor sit amet, ea ludus intellegat his, graece fastidii phaedrum ea mea, ne duo esse omnium. Ne mel quas quodsi. Ea pri semper nostrum necessitatibus. Mea salutatus gloriatur persecuti eu, augue oportere efficiendi at eum. Ex eam mutat falli dicit.

157 |
158 |
159 | 160 | 161 | 162 | 5 163 | 6436 164 | 6448 165 | 10 166 | 6436-6448 167 | 29 168 | 2009 169 | 10.1523/JNEUROSCI.5479-08.2009 170 | 171 | 172 | Baumann 173 | Markus A. 174 | 175 | 176 | Fluet 177 | Marie-Christine 178 | 179 | 180 | Scherberger 181 | Hansjörg 182 | 183 | 184 | Journal of Neuroscience 185 | Context-specific grasp movement representation in the macaque anterior intraparietal area 186 | 187 | 188 | 189 | 190 | Washington, D.C 191 | National Academies Press 192 | 2003 193 | 10.17226/10732 194 | Guidelines for the Care and Use of Mammals in Neuroscience and Behavioral Research 195 | 196 | 197 | 198 |
199 |
-------------------------------------------------------------------------------- /archive/py-jupyter/bibliography.bibtex: -------------------------------------------------------------------------------- 1 | @article{kluyver2016jupyter, 2 | title={Jupyter Notebooks-a publishing format for reproducible computational workflows.}, 3 | author={Kluyver, Thomas and Ragan-Kelley, Benjamin and P{\'e}rez, Fernando and Granger, Brian E and Bussonnier, Matthias and Frederic, Jonathan and Kelley, Kyle and Hamrick, Jessica B and Grout, Jason and Corlay, Sylvain and others}, 4 | journal={ELPUB}, 5 | pages={87--90}, 6 | year={2016} 7 | } 8 | @article{ragan2014jupyter, 9 | title={The Jupyter/IPython architecture: a unified view of computational research, from interactive exploration to communication and publication.}, 10 | author={Ragan-Kelley, M and Perez, F and Granger, B and Kluyver, T and Ivanov, P and Frederic, J and Bussonnier, M}, 11 | journal={AGU Fall Meeting Abstracts}, 12 | year={2014} 13 | } 14 | @article{perez2015project, 15 | title={Project Jupyter: Computational narratives as the engine of collaborative data science}, 16 | author={Perez, Fernando and Granger, Brian E}, 17 | journal={Retrieved September}, 18 | volume={11}, 19 | pages={207}, 20 | year={2015} 21 | } 22 | -------------------------------------------------------------------------------- /archive/py-jupyter/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /archive/py-jupyter/py-jupyter.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Introduction" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Jupyter notebooks ([@perez2015project; @kluyver2016jupyter; @ragan2014jupyter]) are one of the most popular platforms for doing reproducible research. Stencila supports importing of Jupyter Notebook `.ipynb` files. This allows you to work with collegues to refine a document for final publication while still retaining the code cells, and thus reprodubility of your the work. In the future we also plan to support exporting to `.ipynb` files. " 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "# Markdown cells\n", 22 | "\n", 23 | "Most standard Markdown should be supported by the importer including inline `code`, headings etc (although the Stencila user interface do not currently support rendering of some elements e.g. math and lists).\n" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "# Code cells\n", 31 | "\n", 32 | "Code cells in notebooks are imported without loss. Stencila's user interface currently differs from Jupyter in that code cells are executed on update while you are typing. This produces a very reactive user experience but is inappropriate for more compute intensive, longer running code cells. We are currently working on improving this to allowing users to decide to execute cells explicitly (e.g. using `Ctrl+Enter`)." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 4, 38 | "metadata": {}, 39 | "outputs": [ 40 | { 41 | "data": { 42 | "text/plain": [ 43 | "'Hello this is Python 3.5 and it is Tue Feb 13 10:56:10 2018'" 44 | ] 45 | }, 46 | "execution_count": 4, 47 | "metadata": {}, 48 | "output_type": "execute_result" 49 | } 50 | ], 51 | "source": [ 52 | "import sys\n", 53 | "import time\n", 54 | "'Hello this is Python %s.%s and it is %s' % (sys.version_info[0], sys.version_info[1], time.strftime('%c'))\n" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "Stencila also support Jupyter code cells that produce plots. The cell below produces a simple plot based on the example from [the Matplotlib website](https://matplotlib.org/examples/shapes_and_collections/scatter_demo.html). Try changing the code below (for example, the variable `N`)." 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 6, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "data": { 71 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYEAAAEACAYAAABVtcpZAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xd4XNWZ+PHvmS6NRr1Xq1rutmzZxlVuGJtmgwkQwIEA\nSTaQzS5kf8lmG9mS3WxIZwOh94TQm40xxgX33iVbtixZvY/q9Dm/PwS4qI40siTrfJ5Hz+OZuffc\nc63Rfe895T1CSomiKIoyOmmGugKKoijK0FFBQFEUZRRTQUBRFGUUU0FAURRlFFNBQFEUZRRTQUBR\nFGUU80sQEEI8J4SoFkIc7ebzbwohjnz5s10IMckfx1UURVEGxl9PAi8Ay3v4vAhYIKWcAvwn8Iyf\njqsoiqIMgM4fhUgptwshUnr4fPdFL3cDCf44rqIoijIwQ9En8ACwfgiOqyiKolzGL08CfSWEWATc\nB8y7ksdVFEVRunbFgoAQYjLwNHCdlLKxh+1UMiNFURQfSSlFf/bzZ3OQ+PKn8wdCJANvA/dIKc/2\nVpCUckT+/Nu//duQ10HVf+jroeo/Mn9Gcv0Hwi9PAkKI14E8IEIIcR74N8AASCnl08C/AOHAH4UQ\nAnBJKWf649iKoihK//lrdNA3e/n8QeBBfxxLURRF8R81Y9iP8vLyhroKA6LqP7RU/YfWSK9/f4mB\ntif5mxBCDrc6KYqiDGdCCOQw6BhWFEVRRhgVBBRFUUYxFQQURVFGMRUEFEVRRjEVBBRFUUYxFQQU\nRVFGMRUEFEVRRjEVBBRFUUYxFQQURVFGMRUEFEVRRjEVBBRFUUYxFQQURVFGMRUEFEVRRjEVBBRF\nUUYxFQQURVFGMRUEFEVRRjEVBBRFUUYxFQQURVFGMRUEFEVRRjEVBBRFUUYxFQQURVFGMZ0/ChFC\nPAfcAFRLKSd3s83vgRVAG3CvlPKwP459tXO5XJw8eZJTp85y6lQxNTUNeDxeAgKMjBkTT3Z2KuPH\njyMhIWGoq6ooyggkpJQDL0SIeUAr8HJXQUAIsQJ4WEp5vRBiFvA7KeXsbsqS/qjTSGe329m48XM2\nbNhJa2sAen0UFkskJpMFITR4PE5aWxtpa6tHykqysqJZvXo548aNG+qqK4pyhQkhkFKKfu3rrwuu\nECIF+LCbIPAUsFlK+caXr/OBPClldRfbjvogUFhYyNNP/5m6OjMxMeMxmSw9bi+lpKGhlKamEyxe\nPJ7bbluF2Wy+QrVVFGWoDSQIXKk+gQSg9KLX5V++p1xm585d/Nd/PYfTmU1KyqxeAwB0fAEiIpJJ\nSVnGtm11/Pznv6exsfEK1FZRlJHOL30C/vbYY499/e+8vDzy8vKGrC5X0r59+3nqqQ+Ij1/Yp4v/\n5bRaHcnJ06moyOfxx5/kJz/5ARaL7+UoijK8bdmyhS1btvilrKFqDioAFqrmoAtqa2v56U9/RXj4\nAgICggdc3vnzh5g928KDD34LIfr1lKgoyggxXJqDxJc/XfkAWAsghJgNWLsKAKOVlJIXXvgLGk2m\nXwIAQGLiZLZvP8ORI0f8Up6iKFcnvwQBIcTrwE4gSwhxXghxnxDiu0KI7wBIKdcB54QQZ4A/Ad/3\nx3GvFmfOnOHkyVpiY7P8VqZGoyU8fCpvvfUJo/HJSlGUvvFLn4CU8pt92OZhfxzrarRp03aMxjED\nbrapr2/gZNFpXB43qbGJpCSnUFp6lKKiItLT0/1UW0VRriZqxvAQczqd7Nt3kujotAGVY7PZ2Vdw\nFG16JJZJyRTUlVJdU41Wm8DBg6pJSFGUrg3L0UGjSVVVFVKa0WoH9qtobW1FG2wiKDwEgMC4cBqb\nrCTERlJQUOyHmiqKcjVSQWAIVVRU8OY7b3L2XA2I88TFxqPX9+9XEhgYgLvFjtNmR2cwYKtrIig8\niaCgCEpKdiOl9PsoIYfDQWFhIXa7naCgIDIyMtDp1FdKUUYS9Rc7RDZv28yG/euw69txB0vOt5ZQ\ntK+I3Cm5/ZrtazabmTwmi2P7T+HxekmJjiMxIRGNRuB2e3G73ej1er/U3e12s27dJtatO4TTmQxY\nkLKB4OAPWb36GhYunKuGpSrKCKGCgB+1tbVRXFxMaUUp9dZaPF43AcZAkmJTiI+PJykpCSEEFRUV\nbNi/joXfmcvZI0UUHKgkdkw8DZUNHCs4xqyc2fTnGpoQH098XDxSSjSaiwvw31OA1+vl+ef/yvbt\nGhISHsJovDAZrb29nueee5fGxmZWrVqhAoGijAAqCPhBZWUlW3Z8zpEzBwhLCSI4PgBLdhB6rYY2\nm5XdVadoPNSK0RnEghmLqGuoI2FGLIFBAQRaAtBoHQCEx4Zxpvgc7e1t/c79IwSXXHwdjnaCggLQ\narV+OdejR4+yfbudMWO+hUZzaZmBgRGkpNzN++//ienTJ5KcnOyXY/bGarVy+OBBrHV1uF0uAoOD\nyRo3jvT0dBWIFKUXKggMgNvtZtOWz9hyeCPp82JZccNcjCZDl9tKKamtaGD79g2c2FLE5FUTAIiI\nC0fKpo6NhEBn0uFyufxWx9bWOjIykv12MVy/fh8hIQs7BYCv6HQm9PqZbNmyj7VrBzcIlJSUsHX9\nes7u3csYr5cwgwGDELS4XLz99tto4uOZs3Ils6+5xm9BUFGuNioI9JPdbueF15+jKbCCa/9mNgFm\nU4/bCyGITogg+vYIjJHw2TufExYbRvqkVALMXhy2NrQ6I642t18zgLa21jBx4iS/lOXxeDh1qpKU\nlIwetwsPH8uxY3v8cszu7Nu7l4+ffJJpBgN3JiSgv+wiP11Kqlpa2PenP1Fw6BB3PfggJlPPvyNF\nGY3UPIF+8Hg8vPjn53HE1LPw9lm9BoDLzViQQ87KTNb/5SOK80vIXT6WhsoiyvIrSI5J9lsHrsfj\nRohKpk/P8Ut5F2Ye9/xUIYRmUGcpHzlyhPX/93/cGB3NxLi4TgGgow6CuOBgrk9LQ+7fz2vPPIPH\n4xm0OinKSKWCQD9s3raZRmM5s1ZO7Vczi16vJy8vjykLM3jtv16n+kQ1FScPEaWPIDPdf6kjqqtP\nk5s7lrCwML+Up9PpSEmJoKnpfI/bNTYWkZUV65djXq69vZ13n3qK66KjCQ0I6HV7jRAsHDOGlr17\n2bN796DUSVFGMhUEfFRTU8Om/euZeePkAbWzm81mrr1uGdffvYhUSxr/+uOHMAe0XDaqp/9sthbg\nHGvW3OiX8r6yYsUMGhp2dHun7/W6sdv3sHhxrl+P+5WDBw4Qb7cT4UOTmUYIpkdGsuPjj1UeJUW5\njAoCPtq++wtSZkVjtgT6pbzca6dR1VbKggVzSU6G6urCAZfp8bipqNjN3XevJCoqyg+1vGD69Bwm\nTWqjpGQ9Hs+lHdhut53i4rfIywsflFxFUkp2fvwxE/rxZBMXHIynooKioiK/10tRRjIVBHzgcDjY\nd3IXWTmpfitTp9OSMDWCQ0cP8rd/ez8BASVUVZ3pd3lut5Pi4m2sWDGR+fPn+a2eX9Hr9fzgB/cw\nf34LZWW/4dy5jykp2UZx8XtUVf2Wm24KYu3aNYMyNLOhoQFHTQ2xwb6n2xZCkKbRcDo/3+/1UpSR\nTI0O8kFZWRnmGAOBQb23RfsicWwcBZ+c4LplK/jpTx/mN795huLiWhITc9DpjH0ux2qtoqHhIKtW\nzWT16psGbYy8yWTi/vtvZ/XqBvLz82lrsxMSEseECUsJCgoalGMC2Gw2AjT9v28x6vXYmpv9WCNF\nGflUEPBBRWUFwfH+aQa6WERsKLtqj+HxeIiMjORf//URPvpoPR99tBGNJpmYmEwMhq4Dj5SS5uYa\nGhsLCQ+380//9C3Gjh3r9zp2JTw8nLlz516RYwFotVo8A2jT93q9aP008kpRrhYqCPigoakec5R/\nnwIA9AY9WpOGtrY2goODMRqN3HrrKubMmcW2bTv5/PNNuFwBSGlBowlECPHl8M8WoJn4+GBuu20h\nOTnTruqx8MHBwbQBLo+ny2GhvbG6XKT5uY9EGZhTp05RU1NDVlYWMTExQ12dUUkFAR94vB6/jd65\nnNAIvF7vJe/FxcVx++23snr1jVRWVlJRUUFtbQNutwez2URcXCzx8fFERESMivQIZrOZjNxcTh86\nxIS4OJ/2dXo8nNNouGXatEGqneKrXbv28tSreyAgi0D3Szz2j99SgWAIqCDgg6CAIGranX4vV0qJ\ny+bu9i7eYDCQkpJCSkqK34891Gw2G6dPn+5YD0GrJTIykrS0NDTdtP1fs2QJ7+7ezXgfU2Ofrqkh\n65prCAkJ8VfVlQHasfckIYkrCI/J4NxJSWFhoQoCQ0AFAR/ExyZw+MQOv5fbVN9CiDnsqm7KuVxD\nQwPbtm/i6MktJKRKLKEC6RXsOerC/XEks2ZcxzWz53TK+ZORkUHg2LEcLCpiekJCn47V2N7OIZeL\nby9fPhinovRTWnI0R7YcRCLBVkh0tPr9DAUVBHyQkJBAw/oWPB6PXxOSVRZXk5rQ87h6m83GwYOH\nOX68hJYWO0ajnpSUCGbPziE6OtpvdbkSysvLefnPv2b8DCf3PJxEUNClI6Aqy63s3PwKhWePcdcd\nD2AwXEjKJ4Rg7UMP8dR//zeUl5MTH9/jE0Fdaysbamu54eGHr1hWU6Vvbrh+KUKziXMlO5lzz2yy\nsvw3W17pOzHcZlAKIeRwq9PFnnzhCUJnCVLH++eCIqVkw9M7uHvJg2RkdE7M1traykcfbeLzz/Nx\nucYSGDgWnc6E1+umvb0Cj+cAkyaFc8steaSm+m/+wmBpbGzkqef+k7wbDWSM7T54eb1eNrx/Bp0j\nlztvv++SC72Ukvr6el598kkcZ86QbTIxNjr6685iKSWVzc2cbGykwmTi1u9/n8mTJw/6uSnKUBFC\nIKXsV8egCgI+OnHiBG/ufIlr75/bbbu1L0rPVHB2XR0//sFPO93R1tXV8atfvUpV1QTi4uZgMHRO\nleD1eqirK8BuX89DDy1h+vTB7/h0OBwUFBRw9mwJhYWV2GwO9HodKSnRZGYmkZ2d3W3b+wcfvY0n\ncAvzFqf1ehyPx8srfyzg9pv/heTkZBwOB4cOH2LHvs+os5aj0QharU5MDhPUWwk3GtEANikJiI1l\nzsqVTMvJITDQ/8N6FWU4UUHgCpJS8vRLTyEyW5k8N3tAZTkdLj7543buv+mhTmkWWltb+fnPn6W+\nfgFxcb1nAW1vr6em5kV+8pPryc4eWL26Y7PZ2LDhczZsOIDdHo1WG09QUBRarQGv10N7ez0ORzUa\nTTHXXJPBqlXLL0lb4XA4+MVvHuWehxIIsvSt/+PA7vO0lOewZNH1PPfqEwTENjFhVjzxyR0joqwN\nrZzYX8rpva2sXHg7aWlpBAQEEBYWNipGTCkKDIMgIIS4DvgtHWkonpNS/uKyz4OBV4FkQAv8Skr5\nYjdlDesgAB1NGr959n+ZfEsqiem+DVX8isfjYdtf9zHOMoNVN6zu9Pk773zMhx/qSEnpe2dZU1Mp\nQrzBL3/5935fROXMmTM89dRb1NfHExube8mykpfzeJxUVR0HDnP33YtZsKBjzeHjx4+z6+j/seqb\nfW/7tdmcPP2LM1hCE0ifA5Nndt3kVV3RyGevneOBO35EUlKSr6enKCPaQILAgNszhBAa4AlgOTAB\nuFMIcfmt6EPACSnlVGAR8CshxIjtlA4LC+P+b3yPw++c5dzJntMqd8Vuc7D1jb3Ek8GNK27q9LnD\n4WDjxuPExs7xqdyQkCQaGiIpKCjwuU49OXr0KD//+eu4XAtISVncYwAA0GoNJCTkEBFxK88+u5t3\n3/0IKSVtbW1YQn37ygUEGLA212CMbuw2AADExIcxbWkEn21d51P5ijLa+SOB3EygUEpZIqV0AX8B\nbr5sGwl8deWwAPVSSrcfjj1kUlJS+P5dP6TkMytfvL2P9lZbr/tIKSkuKOPTJ3cyKXQWd39jbZd3\n7EeOHMVmS+31YtuVwMBcPv10n8/7def8+fP87nfvERFxE2FhvnWGm0whpKSs5t13T7F16/aOtA++\nrusioaK6gkmzE3vdNGtSIucqj9HQ0ODjQRRl9PJHEEgASi96Xfblexd7AhgvhKgAjgA/9MNxh1x8\nfDyP/s3/Y6JlNhuf2MsXb+3j3MnztFhbv85b73a5qS6t5djOAj5+YhsVm1t5cPUPuHHlzd022RQU\nlGIy9W+4XEREFidPlvX7nC7mcrl45pk3CQhYgNkc2a8ydDojCQkreO21zWg0GirPu33K6V9WWo/L\nLUkY0/vxdTotMWMCqKio6FddFWU0ulJNMsuBQ1LKxUKIdGCjEGKylLK1q40fe+yxr/+dl5dHXl7e\nFalkf+j1elZcu5JFCxZz+Mhh8o+cYOf6EzS3NaHRahBSQ1xUPKkJGSy/+XaSkpJ67bBsa3Og0/Vv\n4phWq8ftlrjdbnS6gf16d+/ew/nzQaSm9rymcG9MphCEmMbu3UfRE09pSSPJY8L7tO+RfTVERCT1\neSSWRivUMpLKVW/Lli1s2bLFL2X5IwiU09Hh+5XEL9+72H3AfwNIKc8KIc4B2cD+rgq8OAiMFCaT\nidmzZjN71mygo+PX4/Gg1+t9HqViMuk7LdjSV16vByHkgDuGvV4vH3+8i4iIvAGV85WYmIns3/8y\n37xrLrs2v0HC2lC02p4v7DXVLZw/rScuNoHWFhtBlp6T90kpaax2EJKjUkMoV7fLb45/9rOf9bss\nfzQH7QMyhBApQggDcAfwwWXblABLAYQQMUAWcFUv8aTVajEYDP0appicHIndXtr7hl1obi4jKWng\nCeUqKyupqZEEB/dv9NPltFo9Xm8qBr2B8IBr+Pit07hc3d+x11S38P5rpay64btcM30J+Qd7//+o\nKmtA5wq7KnMsKcpgGXAQkFJ6gIeBT4ETwF+klPlCiO8KIb7z5Wb/CcwRQhwFNgL/T0qpeu+6kZub\ng0ZzDI/H92R1Vus+VqyYMeA6dLSr+zcdhckUy5kz5XxjzVpC9At46YnT7Np2jtZWB/DlTN9yK5+8\nV8jbL9Rww7IfMHnSZGbnzuX03lYaartfEMblcrP7k3MsnH2dmh+gKD7wS5+AlPITYOxl7/3pon9X\n0tEvoPRBcHAwc+aMYffuwyQkzOzzfg5HM0bjGaZOvWHAdSgvr0EI39fy7YnZHEFJST46nY41t9xF\nZeVi9uzbzsu/34rHa8frhfDQOGZOv5Nbr53+9Uzf6Ohobln+bd556RlmrYwnLTvukj6C6opGdq47\nS2b0fGbmzvJrnRXlajdix+pf7a6/fiF7975Mc3M8wcG9D4/0eJyUlf2F++6b45dspE6nC63Wv6tw\nabV6nM4LI4Pj4uJYddNt3HzjGlwuF1qtttu+jCmTp2AJeoRPt3zA3k8OEJsWiEYjaKxyINuDybvm\nTmbNvEY9BSiKj1QQGKZiY2N59NFVPP74n3E6byQiYmy3Fzi73Up5+V+5+eZ4Fi2a75fjBwYacbvt\nfinrK263g6AgQ6f3hRCXZArtTlpaGt9L+zuqq6upqKjA4/EQNiGM1NRUv+RxUpTRSAWBYSwrK4t/\n/ufbefbZDygp+RyDIZeIiOyvs4i2tJTT3LyfwMAS7r9/LgsXzvXbnXBCQixwoMvPvhrn7+uxWltr\nmTEjfqBVIyYmRi0+oih+ooLAMJecnMzPfvYQxcXFbN68jyNHttDW1rGeQHx8OGvXTmfy5Fv6dCft\ni4SEBKRch7xoBa/6+nOU1e6g2VEECMICskmKmUNISN8Wd3E6K8nMVCmdFWU4UUFgBBBCkJqaekXX\nC4iKiiIjI4SqqmIiIlIpLdtLlWcjGdeOISp5PtIrqTpXSv62F0i130pMzLgey3O5bOj1pUyYcOcV\nOgNFUfpCNaQqXRJCcP3182hq2k97eyOlrZ8yY80MYtOS0ep06Ax6EsemMX3NVM7UvIvL1XPupKqq\ngyxePFnl9leUYUYFAaVbkydPZto0M8dPvkNiTiQmc+cLeFBYCFHjzNTUdJ+5tKWlisDAQm64Ydlg\nVldRlH5QQUDplkajYe3aW5G64xh6SGgaEm+h3VHf5Wc2m5W6uk/47ndXYbH4nhVVUZTBpYKA0qOI\niAhuXXUt9rZiWloqu8wAam+2o9d2XvqysfE81dXv8dBDy5k4ceKVqK6iKD5SQUDp1dK8ZUQRTlho\nE1ZrPjab9UKqbKeLyqP1xERfWEeora2ec+c2YjBs45/+6XZmzswdqqoritILtcaw0ispJa+88TJl\n3kISp0dTXlmH1erE1qLh9LYSNFVZJMbNwOVqRIgagoLaWb58BosXLyQgoOfMn4qiDNyQrzHsTyoI\nDE9ut5tPN21gx+FtGMK1tLe1Yy1rJTE0lZTkLMBLe1sDlaXH0NCKTqtBqzczZfoyZsyce8mC84qi\n+JcKAsoV43A4qKqqQqPREBcXh06no7i4mDdf+w0Z8c3kTgojIc6CEIL6hnbWbznHwQIXk2fcyJw5\n80lOTvZLbiNFUS5QQUAZMhUVFbz63L+zZqmRtJSOrKONVht7jlbyWX4DrohQvAEmzpa2E2pOJkoa\nWTgxhzkzZhEbGzvEtVeUq4MKAsqQee5Pv2RG2lmmTIhFSsnWfaW8eaiWwCkZJOSkERQRBIDD4Wbf\ncRuTJ82l5nghzftPsXTsVFatvFElf1OuKI/HQ21tLW63m+DgYIKDg4e6SgM2kCCg0kYo/VZVVUVz\n3QkmXd+xktcnX5zjw3IX476zgoDgSzuEjUYdUaFerK3NZCychWv2VDa/+Smtb73B3WtuV4FAGXQO\nh4Od27dxYPs6AmjCoBM0tEkSMmYwd9GKK5qWZThRTwJKv2367FNE459ZPDeFA8cree5wIxPvXYIh\noOtkds3NDk6XGZkxKw8Ar8fDsVc/ZEXCeFYuVWsOKYPHZrPx0tO/IcpbwPzJMUSHd8xrcbk8nCiq\nZdNxB4tWP0TO9IGvyjcU1JOA0i23282pU6doaGigrc2OyWQgKMhMdnY2QUFBAyq7rbWRBIser1fy\nzu4K0u5Y1G0AgI6nAZfT8fVrjVZL9pprWf+Hv5I3Z77KK6QMmvffeo1UQyHXzkq9JAW6Xq9l6thY\nkmNtPP/uk8TG/Qfx8QNPdz6SqCBwlbJarezavZ9PNh+i2RMDpnjQmJBeF7jOo7N/xoLcDBbOzyU5\nOblf6xDodAacTg+ni+ppDgkhNa7n5Si9Xi8azaUrhxnNgeizEzlw6CDz587zuQ7K8NTa2kpLSwtS\nSkwmE2FhYUO26ltDQwOlBTtZsyqp2zqEhwQwJ0OwZ8dmVt921xWu4dBSQeAqI6Vkx47dvPDmNrxB\nU4hKu48US2Sn7dxOO9uKDvP5vg+YNzWStXfd6vOaBEkpGRza6qX9fDXhub2nhahvtBMcltLp/bjc\niWx86wvmzfHfojjKleX1eiksLGTbtn2cPHkOq9WORmNGCIHX68Bo9JCRkcj8+dOYMmUKRqPxitXt\n2NEjTEr0otP13O80NSuG3360lZtuuaPbZU6vRioIXGXWb9jEX9adJn7y9zAFhnS7nc5gIjFjNt60\nXLYf/xDrky/zt3+z1qdAMG7cODZ8GMK5pkrGr+llpS8J5TUexk3uHARC42MosrVis9lUk9AIlJ+f\nz0svvU91NRgMmYSFLSM52XJJQHe5bBQX13D8+G4CAj7m1lvzyMtbcEUutq3NjUSae7/UBQbo0Qk3\ndrsds7lzLqyrlQoCI5yUkvLycsrLy1m//nPW7awiNPMbWE+VEB4WjCXYQmhIaLd/bBqNlpRJN3P8\n+Ie88PKbPPjtO/s8Uken0zFn4S2s+90/MlHfw1dJwtmSJkzm+C6H4wkh0JoMOBwOFQRGEIfDwRtv\nvMumTacIC5tLSkr3bel6fQARESlERKRgt7fw0kvb2bPnGA8+eOegzyY3mszYnJ5et3O7vTjd+H2V\nvuFOBYERyuFwsG/fftat20plpQ2328z+kwUEpH8Ha3MwXquH8+erEJSg07vJSE8iOTmpy1w+QgiS\nJ1zPzv3Ps+DUKcaN63mVsItdM2c+lpdSOXi0nrGZwYQGm+CiFh273U1xWQutrkimTJsG3TT3SLcH\nnU59HUcKu93OH/7wHMePC5KTV6HV6vu8r8lkITX1OkpK8vnP/3ySn/zkQeLi4gatrmPHjefdHZKF\nObLH5saTRbWkZOWg1/f9XK4GfvmrE0JcB/yWjqykz0kpf9HFNnnAbwA9UCulXOSPY49GZ86c4emn\n/0x1tZ6IiGySk6MoKzuJMXI2waFJnbZ3ux0UFDRw+vR5Jk3OJKWLjmCNRos57ho2bt7nUxAQQpAz\naRotljQKy6zgsRJqEQig3Q4tdgOx8ROYNiYNbTcXeUdbOxqnWyWbGyG8Xi9PP/0KJ08aGDNmTr/6\ncYQQxMWNp7bWyP/+77M89tjfEhLSffPlQCQmJmKMGMvh0yVMG9v1LHWH080X+e0svXP0LXw04Bk6\nQggN8ASwHJgA3CmEyL5smxDg/4AbpJQTgdsGetzRSErJRx+t5z/+4zkcjrGkps4jODgagMLzZzBF\nTOtyP53OSEhIHKaAFA4dLGbPngO43K5O20UmjONQQQ11dXU+1Stv2ky85fXkzlpI5oQ8AiNmYArL\nIW7MNVwz91rSM7K6DQAAZYdOMm/CNPUkMEJs376TffsaSU7uXwC4WFRUOi0tY3jttbe7XKvCH4QQ\n3HLnA2w6ZWLP8Qrcbu8ln9c1tvPKxhJSpt1EVlbWoNRhOPPHNM2ZQKGUskRK6QL+Atx82TbfBN6W\nUpYDSCl9u8ooSCl5990PeOONXSQmLiUs7EL7a3NzDc2uQIzmnh+p9TojoWFjqKp2sWf3Adxu9yWf\nazRaCJ7KgYNHfKrbjGk5OPNLcdodhIaGkpCQQGJSElHR0Wh66fjzer007y9gfu5sn46pDI3GxkZe\nffVT4uPn+20kV0LCNHbvruLIEd++d76Iiori3of+mdPOyfzmvVLe/6KE9btKeOnTEl78wkn2gvu5\n/qZbRuXoNH/ceiUApRe9LqMjMFwsC9ALITYDQcDvpZSv+OHYo8aePXt57739pKQsQqe7tOPK4WhD\nGCP69AWT5d3NAAAgAElEQVQWCEJC4qmrr+Do0eNMy5mCuKgR3xgYSXVtkU91M5vNzMuezL7Pd5O9\ncqFPf0jn9x4lIyhy1E3QGal27NiN251CQMClTTd2ezNVVYXYne24vG4MWj1BgcFER2eh1/ecNVYI\nDaGhOXzwwRamTJkyaBfiyMhI7rn/Yerr6ykqKsLj8TAmJITMzMxR/RR6pc5cB+QAiwEzsEsIsUtK\neaarjR977LGv/52Xl0deXt4VqOLw1djYyIsvvk9s7JxOAQDA43GB6Pu4a4EgJDiOkpIi4uOrL8nm\nqdUbsdmcPtdx1XXXc+75P3F2617SF87s0x9y2dF83Dvyue/+7/t8POXKc7vdbNiwh6iojhQfUkoa\nGs5zruwE5U0NyPAJaEzJCJ0er8eJrKtAe+YNUmOSSEmYiMUS3W3ZYWFJFBXtpbS0lOTk5EE9j4iI\nCCIiIgb1GINty5YtbNmyxS9l+SMIlAMX/9YSv3zvYmVAnZTSDtiFENuAKUCvQUCBN9/8ALc7mcDA\n0C4/12r1INt8KlMIDYHmeA4dzufaZVFfDyH1uBwE9JD6oTsmk4mH7/k2f3r9JY7XbSBlwQyCoztP\nUgNob2qmZNdhAvKreOSeBwgN7fq8lOGloqKCtjYTEREheDwujp78nJJWO9rYWVjSstF0MULI42zj\nbO0xzhzexKTEdNLG5HZ5gyCEQIgkTp8+0ykIVFZWUlxcDEBqaqpKQU7nm+Of/exn/S7LH0FgH5Ah\nhEgBKoE7gDsv2+Z94A9CCC1gBGYBv/bDsa96jY2N7N59koSEFd1uYzSakY6zSNnzELhO+xkCsVp1\n1NTUfD1Ez9FeR3Rk/1LrWiwWfnDvg3yxczufvbKR4ogAQqdkYgoOQgiBo60d64kiREkdiyZPZ9ED\nt2CxWPp1rKHg9Xr55MP3kFJy3Y2rRtWsUugIAlKG4fW62X90HRUiitBJdyA03V9GtAYzIQmz8URN\n5EjBm7hc2xmbOa/L72lgYASnT59n6dKO121tbTz/4lscONaAMGQjkeDYxYwpkXz73jVqTomfDDgI\nSCk9QoiHgU+5MEQ0Xwjx3Y6P5dNSygIhxAbgKOABnpZSnhzosUeDvXv3IWUsWm33v6rg4GgsunYc\nbZWYgnxrWzcawzhzpoS4uDi8Xg80HWbG9Hv7XV+j0cjSRUtYtCCPgoICDuQfo6m9Gq+UxAQEcmPW\nbCbdMmlETsipqanhyOa3EUimzbxm1PVjlJVVo9OFcLxgCxVEEJp5U59vOrSGIELH38nJE69gDjhG\nUtLkTtuYzRGcP58PdATcJ558jVOlqaSMvwfx5QRG6b2Og6c/w/bka/zokQdGZUeuv/mlT0BK+Qkw\n9rL3/nTZ68eBx/1xvNHk0KFThIQk9riNEILM5HQOlh/yOQiYAizUN1Tg9rhpqChganYUkZFdN+P4\nQqvVMmHCBCZMmDDgsoaL6Ohopi5eg5SSmJhe0mRchRwOF3Z7K0UNdYRM+Z7PF2CNzoQlaw3HTj5P\nfPy4ThPMNBodTmfH0OXCwkLyzwpSxi+95DhCoyEpfRknTj7NmTNnyMzMHPiJjXJqJY9hzOv1cu5c\nGUFB4b1uGxeXiba9AJejyadjCAQCAy1NzbRW7mLZotz+Vveqp9FoWHHTalbefMuoawoC0Ot1VNac\nRUTldNn+36cyAsJwmpOoqencHej1etB/mX7k0OEC9OYLI4WcTifttnag46ZHFziFQ4cL+nkmysVU\nEBjGmpubcbk06HS9j/zR641MzRpPS/FbeNx2n44jpZ6iI+8zZ0IA2dnZve+gjEqRkcGUNpRjjpky\noHIMMTMoLD3ZaXJYe3sj8fEdeYTsDje6i4aWOl1OHM4Lo9Z0ehMOx6XzXJT+UUFgGHO73XT0pfdN\nUtIEJiaF0VT0Gm5nc5/2kV4PrRVbSAk6y31rb1PLPCrdcrvdeC3x6AwDW4woIDSVBrsNh6P1kvfb\n2+sYN65jZFBmejz21rNffxZkDiIs5MIoMnvrWdLTBi/f0Gii/uKHsY4mB9+m0memT2d6WixtZ5/D\nWr4Zl72xy+28bjvNtQdpKnyeGE0h37xt5RXN8a5ceXa7ne3bt1NZWdmv/S0WC1pTAG6P7/NILiaE\nQBgsuFwXnlillHi9ZaSnpwEwbdoUArWnaW4s67R/U2MpZt0ZcnKmDqgeSofRO01uBLBYLGg0Ljwe\nd4+jgy4mhCAleRLRUWMoKz/FmZIXadPFI03xaLQmvNKJcDWhaS0gOSaKMdOm0NSkH5UdnaPNnt27\n2Lf9cY4emsn3f/BvPu8vhCAuIRprWx0hwQMbGSU0OrzeC805zc1VxMfrSUvrCAKBgYH83UO38Kvf\nv461PofQyI6khtbakxg5zKM/uBWTqeeZyErfqCAwjOl0OpKTY2loqCckxLeLdECAhcyMGaSnTaWu\n7jw2mxWny4VOq8VgMBEVtRKDIRApJU1NewY1la8yPKSlZ3D08DQmTp7br/0DAgKIjQrG2tCAxxOJ\nVtv/Yb7Sbfs6nYSUkrq6gzz00KUpRzIzM/n5zx5g5859HDj8IW6Pm5lz4pg75w4SE3seMaf0nQoC\nw9zkyVm8//45n4PAVzQaHdHRad1+3txcQ2JipJp4MwokJSXxg7/7z37vHxMTg8n5OeOyZ3LiZCmh\noWmX5J3qK7e9CZ27DaOxo2+hquokkyYFMnNm55FpYWFhZGenU9Fcx76iIipLbWx87RSxAQGszJ1F\nzrRpKgX5AKkgMMzNnp3Lu+9ux+ud3GmRdn9obDzLmjXz/V6ucvWJjY1lXHIwxXoXERFgtVYRbPH9\nCbK15jDj4tLRavVYrRVotSe4776HOg1K8Hq9vPXhh6wvLiJwVi5JN61EZzB0PL2WlfP8/oN8vGcX\nj9zzLb/MbRmtVMfwIJBS4na7/ZIfPSYmhilTUqip8S2zZ1/YbC2YTI3k5HS9DsFgcToH1rGoDJ3r\n8nKxlR9g1qxpBAW10NRc3pHOoY+k142oPURSwgTq60uw2bbxox+t7XKJyQ8++YR1dTWk3n8vCTnT\n0H05y1wIQWhSIumrb6Jl7jU8/vKLtLa2dtpf6Rv1JOAHbreb/Px8Th85QuXp09SWl4HHixSCkKgo\nErKyGDNxIlOmTOnXo+sdd6zin//5dzgcCRiN/mm2kVJSUbGX73zn+iv6OF1WVsaBY0e4cfkKNRx1\nBBo/fjxRus9oqT7HvLm5HDx4jMqq0wSZk9Hre/8eNZftJMYcRG3tMaKjG/mHf3igy6yhjY2NfHT0\nMKnf/+7XF/+uxE2dwtmqKr7YtYsVy0bfqmD+IAZrNZ/+EkLI4Van7rjdbrZv3cqeDz4gsrmJbKOB\nWIuFaLMZrUaDlJL6dhtVrS2cbbdRZDAyedkylqxc6XMb/IYNG3n11V2kpi5ECA1WayVNTVUIoSUy\nMhmzOcyn8srLjzF2rIdHHvn+Fb0Yf/TRRl5/fTvTpiXy939/36jO4z5SVVRU8O+/fRXTuG8QEpNC\neVk5R44W4nIFYDRGYjQGoblsfotE0li6E4reY0pWNDfdNIebblrR7bDkdZ9+yrtuO6lLl/Ran/b6\nelpffp3HH/3RqP0+CSGQUvYrkdLo/B/zg8rKSt5+9lmCz57hzpgYIiPGdNpGCEGkOZBIcyATgVaH\nk13rP+b/du7kxu98x6fZuUuXLqa0tIJNmzbjslcTaipm0hhwuuH4WYEwzSQ9czEdq332rKIin8jI\nBh588OErfje+ZctxYmLu59ixv9LY2NhlM4AyvMXHx/Pjv7mNXz39JpXNecSnTSM2Lpbq6mqKispp\nbCxBSgMdlxeB19VGe+Vuop3H+Pt/+AbLli3pdT3hvYWnCV+5vE/1CYyIoMoSRGVlJUlJndfYVnqm\nngT64cyZM7z5+C9ZotUwITra50RapdYmPmxoYM79DzBnft87Zd1uN3/70P2Ea44wPzcbo6HjacLl\n9vDJ9ioa3deRPKb73D9ut4OysoMkJXl49NHvDUke/y++2MUrr2xi5swMvv3tb6gmoRGspqaGt97/\nlP0FlRA5lcjUHAKCwpBAS0sz1ppSms4fwNhWyPJ5k/nGLTf2+Qn4J7/9Nbrb1xDYx8Vfil/7Cz9e\nkEd6evoAzmjkUk8CV9D58+d565f/y61BZhJ7uZvpTlJoCHeZjLz+zNMYjEZmzLx8Nc6u1dfXk54o\nuG3pDI4ePYXdFoQ5KAK9zsCC6RG8vG4n3uRpaC7L7+7xuKmpOYvDcYYbb5zFDTd0/xg+2ObPv4b5\n868ZkmMr/hUdHc33H7ybhoYGdu7ez6adL1Db0o5EiwYP8dFhfHtVDtOnf8Pn5s8gYwDNbe19DgLe\n1jY1eayfVBDwgcPh4K0nn2SF0eh7AJASLnpiCDGZuD0+nleefZaU1NQ+NYtUVFSQlqAhOSmR6KhI\nzp0r5mxRCW63ASFMGLXtNDZWEBAQgtvtoLW1AaezEY2mjlmzxrF8+XcZM2aMj2etKD0LDw/nhpXX\ncsPKa/F6vbjdbvR6/YBy/c8dP57Xjh0nNLn35p3myioiXE414bGfVBDwwcaPPyaxqpLM1DF93sft\ncnHi2EGsDVWYg8KYOCUX05ejccIDA5hntfLuSy/x4KOP9vpHYzQaaW3vaCozmUyMG5dNZmYG9fX1\n1Nc3otnUgMFQiEajJzTURG5uMunpk8nKylJLOCpXhEaj8cuCQbnTp/Pn3/8O+/y5mIK7X+lOSknN\n7j3cMz1XNS32kwoCfdTa2sqRdev4XmKCT/udO1tIABVMnhpKebWVU/lHmJIz++vPc+JiOXz8OEVF\nRb22Z2ZkZPDhW4HUNbQTGd7xeK3T6YiJiaGyTrJ0xSzufeDvfT85RRlmgoKC+Ob8BbzwlzcZ883b\nMQZ1zlwqpaT0i+2k1TUwZ9WtQ1DLq4MKnX10YN8+xno9BOh9W0zDbmslPNiA0AjCQ0zY21su+VwI\nQU6AkT2ff95rWQaDgWXX38crH1STX1iL1ytxONzsOVTOp3s1XLvydp/qpij+JqWkvLzcLxMlF86b\nx9qJkyl/5nlKtmzF1tiIlBKPy0X1iROcffk1UgvP8cO131IZcAdAjQ7qoz/++7+zxNpIUqhvfQFV\nlRWcP72HhCgtNY1uQmImkpaRdck2TreH31dW8tMnn+rTOOfTp0/zxefvU37+JEKjZeyEOSxcfL3K\nBKoMuerqat5++2PWrv0GQV3cvfdHbW0tO/ft4/NjR2lpb0er0TApJYWlM2cxduzYUbnK2+UGMjpI\nBYE+cLvd/Px73+OHCXHo+/GFq62pwdpQh9kSTFx8Qpdt/8+XnGf1v/8HCQl9b27yeDxoNBq12LYy\nrNhstkGbhe71ejvWI1Df+UuoIaKDrLa2llDp7VcAAIiKjiYqOrrnbaSkqqrKpyCg7oCU4Wgw05Co\nzl//U/+jfeBwODAN8p1HgOg4jqIoypWkngT6QAjh4yKPvvNKdZejKNBxM5Sfn09TUxNGo5Hs7Gw1\nxHkQ+SUICCGuA35Lx5PFc1LKX3SzXS6wE7hdSvmOP459JVgsFpq9gxsGmoUgw08daYoyEkkp+XzL\nF7y9bhc2fQrSFIVwNyP+spUF09O447Yb1azgQTDgICA6MpY9ASwBKoB9Qoj3pZQFXWz3P8CGgR7z\nSgsLC8MVEECb04nZDxNhulItOxJzKcpotX7DJl7fWETi1O8Rbb4wCs/jWs6WE59S9/Sr/PBvvoXe\nx2HaSs/88SQwEyiUUpYACCH+AtwMFFy23Q+At4DuM5wNU0IIErKzKS7IZ0JMzx28/dHQbsMTFERY\nmG/poBXlatHQ0MCbnxwgNHstlTV1tDQVYGtvwev1otXqCDTH8dnBU0zduo2lfUgvrfSdP4JAAlB6\n0esyOgLD14QQ8cAqKeUiIUTfsqUNM9MXLWL3wYNMGISyD9fVMm3VLWrY2wjidrtxuVwYjUbVlzNA\nUkre+OtbFFQKQsUeosMgPsRAYIwejUaD2+Omrb0Sl8XML3/1CypL81l87WqfRtIp3btSHcO/BX58\n0eser3aPPfbY1//Oy8sjLy9vUCrli3HjxrEuLIzK5hbigi1+K9fmcnFUwvfmzPFbmcrgaGhoYNeu\nfWzceACr1YYQeqR0ERsbyooVM5kxIwez2TzU1RxR2tra+PDdP7P5s7fJyFxDaroFp92G1+NCI70Y\n9YGYjDqCAg1EhWdS2hLI+KijvP7MQabNuY1FS64dlUOlt2zZwpYtW/xS1oAniwkhZgOPSSmv+/L1\nTwB5ceewEOKrBXIFEAm0Ad+RUn7QRXnDbrLYV44cOcK2X/+Ke1PHoPXT3d/754oJXX0LK266qdtt\nXC4XBQUFtLa2Eh4eTmZmprr7vIKsVit//vMH7N17HiHGERk5mYCAsK8m6NDSUkVDwxH0+rMsXjyR\nW265XqUx6IOmpiZeevZxkixFbN1bwdnGCPTCgdnoQacBtxfaHFoMllgCosYSGZ+O9djLPPMvU2ht\nc/LeplJEyDxuv+v+Ubui2FeGdMawEEILnKKjY7gS2AvcKaXM72b7F4APuxsdNJyDgJSS1597DvPO\n7SxLSRlw882x6mp2hUfy0L/+a7edXSdOnOSp9z6iNSoBwiKguoIYWxMP33kbiYmJAzq+0ruqqioe\nf/xlrNYJxMVNR6vtvlPS5bJRXr6NzMwGfvjDe/2WNuFq5HA4+MOvH8PVsBONq5kYcxtOTShjMsZi\n0F+4s3c6PTQ02Thb3saeEy0khLn55T8uJshswOPx8vanxYiwJay5fe2obk4d8rQRXw4R/R0Xhoj+\njxDiu3Q8ETx92bbPAx+NxCAAHVPin//1r0k9V8TCpMR+f/FO1tTwuc7AvT/9KdHdzCYuLy/n3176\nMyE33YUl9kKu9PqzpxGb3ue/fvA9LBb/NU0pl7JarfzHf/wJm20u0dHj+rSPlJKysh1kZpbzyCMP\n+CWt8tXoid89zomdz7Fmnon5UyxoBGzYXYsuYhJGU+cFaLxeD3Xnj6ETTs7WWVh5bQ4Txsbgdnt5\n5u1i5ix/hClTpw7BmQwPQx4E/Gm4BwHoaMd89Y9/xHj8GCsSE7D48OjvdHvYUlZGYWQUax99tMek\nby/99S22hyaRkDOr02cln33MXdGBLF28qF/noPTuiSde5PDhaBITO///90RKSXHxetasieTGG1cM\nUu1GrvfefYePX/5//MvaCJJjL6SYqG1oY+cJG9rgNMyWUITQIJE42ttobyxhfLyTsamhVNQ5+evW\ndubOn8HMqUlU1rTw6gYnD//ofwc1ZcVwNpAgoBqW+8FsNvPAI4+Qcs9anq+uZXtpKS29pHxwuN0c\nrKjgufPn8SxZysM/+1mvWT8PF5UQkdH1YvSW9GwOnyvp9zkoPaurq+PAgXLi46f7vK8Qgri4uWzY\ncBCXyzUItRu5jh09yuZ3H+fRNUGXBACAqHAzC6cEEaUpoqnsIM2VJ2gqO4Kh/SSzM72MTQ3tGK4d\nZeS+5RZ2fLGfY/lVxEVbSItp4cjhQ0N0ViPb6O5NGQCtVsuipUuZOGUKu7dt49mNG4lxOYmRXqJM\nJvQaLR7ppdFupxJBhRCkXjOHW5cs6fMSj1oBjcVFBMcnYo68dPlJr9uFYRSOirhSdu7cixDjO63X\n3FcmUwhVVTEcO3aMnJwcP9duZGpubub9N55g8fhmMpLDu9wmNNjErEkm7A43DqcbnVZPYEBIp2bX\nUIuOOxeZeeWzg6QkLiF3Qhgf7FrP7GvUKDtfqSAwQFFRUdx4661ce8MNlJaWUl5WRmlxMW6HA41O\nR0RSEjMTE0lKSvKp/d7pdFJbcI4dnzyFJT6CnLtuJGnmhQXaW04cYvbkrp8SlIGRUvLppweIjr5r\nQOVYLJPYtOmACgJfWvfBX5kYW09aiB6drudGCJNRh8nY8+UpNsJAbrqNdZtOcPvN02i1nqetrU0N\n0/WRCgJ+YjQaycjIICMjwy/lnTp1CpcjkkgRjM0VwdG3PiRp5jW4HQ4q9m4nrb2BqVOn+OVYyqUc\nDgdtbV4iI31bQMjr9dDWVktraw0uVztOZxstLQVYrVZCQjrfzY4mdXV1lJ7awc25Jgx+TJY7b1Iw\nv3mnlAZrNnERgoqKCjIzM/13gFFABYFhSqfTodcL5k6bxP6j+6kvP0bp689AUwPzMlP5xre/pUae\nDBKn04kQfc9P43C0UH5+D9VnNmF0thGMxCQleq+HRtcZ/ufv6onNyGDBypVMmTJlVE5uOrB3Fznp\nGrxuG0aD/7oidTrBtFTYf6SU0KAAWlpaet9JuYQKAsPU2LFjWbIki82bNzIxS8+99/4rcXFxhIaG\nqvHng8xoNCKls9ftpJRUVR6l+NBrxLttzAqMwBxwIeWx2+3A621kWUoKZTU1rPvtb/li3Dju+Pa3\nR91SoGfy93BrbhiNtQ1+fyKakGrinb3lJKVm+GVt49FGBYFhSqPRsHbtndx22yr0ev2onxF5JRkM\nBkJCDLS31xMYGNHlNlJKzp7+hJaTH5BrjsJi7ryd09FKeEQgQgiSwsNJDAvjVHExf3jsMe770Y9I\nT08f7FMZFpxOJ9a6CqLCE2lpNOJye/xafnSYAau1mZA2L+kq1bTP1BDRYS4gIGBUBwCbzUZFRQVl\nZWU0NjZekTs9IQTXXTeT2toj3W5Tcm4bbSc+IDckEYuh645Ip6uK1NQL6cGFEGTHxZFrMvHC449T\nVVXl97oPR/X19UQEC7RaDZbgUFra/Vu+VisIt8DZMjtxcXG976BcYvReXZRhy2q1snv3PrZuPUpV\nVTMaTQhCaPB62wgM9JKTk0Fe3ixSU1MHrbN19uwZ/PWvf8DjmYdWe2nfS0tLJVXH3mZOcDz6boaQ\nulw2TCY7UVFRnT6LDQkh227n9Wef5Yf/+I9XfR+By+VCr+34PVksFs61SaSUfv3deT0eHJ4AlY69\nH1QQUIYNj8fD559v5Y03tuPxZBERcS3JyRF0rEfUwelsY/fuQrZufZsZMyK4++7Vg/KHHxoayty5\nGezYsYvk5IWXfHb22LuM1eox6rrpmJeS1tYSJk9O7DbRX0Z0NOdPn2b//v3MmuXbjOSRRqvV4vZ0\nPMGZzWYMpggarC1EhHVOD9Ff1Q02Ji5cOKpHYPWXag5ShgWbzcbvfvccr7xSSFTUnaSk5BEUFHVJ\nAAAwGMzEx09lzJi7OHo0in/5l//j3Llzg1KnO+64iYSEYioqDnz9XmtrNc6ak8SaO9/hAyAl1qYi\nEuIFqakp3ZYthGBceDjb1q276jszw8PDaWj1fn2eCcmZnK92+O28HQ43heWwIG+pX8obbVQQUIac\nx+Phj398hWPHLKSm3oTR2PukOiE0JCTkotVeyy9+8Srl5eV+r5fZbOaRR+4jLu4EJSWf43S2UVud\nTzyg6eKO0+2y02g9RXyci5zpk3pN9x0XEkJTaSm1tbV+r/twEhAQQKAlmnqrDYCYmBikLoaKmla/\nlL/vZCPRiZNVVt1+UkFAGXKffbaZI0ckycl5Pj/Oh4Ulo9Es4Omn3xyUPD1hYWH8+MffZflyHfX1\nL3G+8D1M0ovX40J6vXg8TtrbG7BaT+JyHWfixBBm5E7tU2e+EIJQoKKiwu/1Hm7GZE3jVHED8GUH\n+fipFFcJmlrsAyq3vKqFE6UGFi671R/VHJVUEFCGVGNjI2++uYuEhCX9bs+Njh5LcXEQO3bs8nPt\nOgQGBnLHHav57W9/xIRMiAiqx+44QmvrPpzOY4SGVDN7diLXLp9LZma6Twv+BHq91NfVDUq9h5MZ\ns+azv9CD19vRBBQYGMi4SXM4XuSk4csnBF9IKSmtbKa41ohVjGP23Dw/13j0UB3DypDatWsvXu9Y\njMaBTYCLiprJRx+tZ8GCeYO26lpAQACxsbHMio8n2E8pi7VC4PH4d9z8cJSQkEBQ9EQO5Z9m+oSO\nYZzh4eFMmLKA/ON7iWyykpoQ3GtOIQC7w82p4mbcmmgwJxGVPIn4+Phe91O6pp4ElCG1ZctRIiLG\nD7icoKBoGhoMlJaW+qFW3TMHB2P3Y7OTAzCPkhngN956D5uOeC5pAgoNDWXGrEV4DOnsPt5CYYmV\npmY7Ho/3kn1dLg/1je2cOGNlf76d0Ngc0sdOZ8cpHTfccveVPpWrinoSuIrY7XbKysqorKigua6j\ns9EcGkZcQgKJiYnDLrtiW1sbtbVtJCd3PSvXV1JGU1FRQUpK96NyBio5K4u6zz4jOjjYL+W1aDSj\nZoJTdHQ0c5bdzRufPc+3rk/GaOi4/Oj1erLHTcKemkllRTlnqstpa7Vi0Eo0GnB7wCN1WILDiYpL\nZmxMDG4PvLSuhLnLHuh2ZT6lb1QQuArU1dWxc/PnnNy6iVjpIl4jifryD6zF5WafV8O7HkH67HnM\nWbqMhISEIa5xh4aGBjSaUL+N7dbpwqmsHNz29YzsbNatX8/An13A5nTSqtMNm9/HlTB3/kKamxp5\n6eN3uPPaRCzmC6vymUwmUtPSSU1Lx+v14nA48Hq9aLVajEbj19+TljYHf/60nOTJtzJn3oKhOpWr\nhgoCI5jX62XHtm3s+utrzDZ6eTglhiBj1xOY7C43R47u4C87tzLhhtUsuW5Ft4vbXylerxch/Ddb\nVgiB2895aS43fvx43rJYaLLZCBlgv8Cp6mqmL1uG0YflSUc6IQQrbljFF5YQnvrgNa6dZmDy2OhO\nNwIajabTUpFSSo4UVLPxsJvZS+5j3gLfRpNVV1ezY9s2tFot8/PyCA/vemGb0UYFgRHK7Xbz1ssv\nYd+9he+mJxAS0HPiLJNex6yUBCY5XXy87m1eKjzN3d9/GNMQJtwKDAzE6/VfIhmXq53QUP8003RH\nr9ezePVqdr34IosHkLai1W6nRAhuWbLEzzUc/oQQLMhbTObYcXzw1otsP3GK/9/encdHVd6LH/88\ns2TfF7KRBQIJAUIACYtsARRBXGivtbjgQqtWa632am1/P3tL723rbV+9vdryq7bWrVZFW6u1Aoos\nYZXLsYcAAB3lSURBVFMwrIEQSEhISEKWSYasJJM5M8/vjwkIkmQmyWQmIc/79eL1mkmec+abwznz\nPedZs8cbyUiNuuzJ4IKWNguFJfXkFVsxhKaz+pH7iY2N7dNnNjY28vuf/5yYtjZsUnLos8946mc/\nG3JVpN6gksAwJKXkg/VvIfJ2sHriGPR96ZLoY+S2CSl8fKqAt/74Avc++pjX5q6JjIzEaGxH0zow\nGAaejIQwMXr04K+2tmDhQo7s3cvxsjIm9aNXima381lVFUvXrOl2bqGRIi4ujgcf/RHl5eXs35tL\n7oYD6O21RIcIDHrQbAJTsx2bLpCx6fO4aXUOSUlJ/Uq8JSUlBLW2ktnVXrTrzBkqKiqYMEGtzqeS\nwDCUf+QIpp1beCAjpU8J4AIhBMvGJfHm8UPszs1loZfuRnU6HdOmpXL48CliYycPaF+a1oFOV0NS\nUpKbouuZXq/n3ocfZt0vf4k4e5aMuDiXv5g6NY3dZ84wdskSFixc6HyDq5wQgpSUFFJS7kPKe2lq\naqKhoQFN0zAYDERGRrplVbbg4GCa7XY6NQ27lLRJ2aflXq9mqovoMGOxWNj8l1dZmRiNQe/8v09K\nianexNH8PPbn5XL48OdUV1djl3ZuSU1g33vrMZvNHoi8e4sXz6K9PX/A88jU1BQwf36GxxbcCQ8P\n57s//jHmxES2nz5Na0fvI1+llFSazWyqqGDCzTezavXqQRvPMFwJIQgLCyM1NZX09HRSU1MJC3NP\nx4Hx48cz69Zb+eTsWbbU1HDdqlUjqkG+N8IdkzgJIZYBz+FIKi9LKX/1ld/fCTzd9bYFeFhKebSH\nfcmrfUKtgfhi3z7K//Ii30h33g1Ss2kczd+P1KqJjzUSGGjEYrFRXdvJ+Y5QsqbOYVdVHXLp11m6\n4iYPRH8lu93Ob3/7EkVFScTHT+vXPjo6mqmvf4df/OJBj6/YpWkaudu2sf2DDwhtbyfe15fI4GAC\nfHyQUtJ4/jymlhYqbTYCEhP5+j33qDVwvaijowMhxFXXGC+EQErZr2w54CQgHNM8FgFLgLNAHrBK\nSnnikjKzgUIpZVNXwlgrpZzdw/5UEujFn/77F1x3vo6xUc6nTy4oOIyBMtLGXfk4XVHZTE19GGMn\nzuDl+g6e+p/nvTYNb319Pc888wIBASsICelbHbvN1klZ2fusWTOVRYu8V71isVjIz8+nuKCAMydP\n0trcjNDpiI6NJTkjg8lZWYwdO/aqmepY0zRqamqora2ls7MTvV5PVFQUcXFxV/TqUQbfQJKAO9oE\nZgLFUsryrmDWA7cCF5OAlHLvJeX3Auo5rB+sViumslKS0p0fvvaOdhrN5cyeEdLtF8/ohGDq6s3Q\n2Y7xfBtms5nISPcM2uqrqKgo/v3fV/HrX7+D1bqIyEjXll20WFqoqNjIzTcnkZPj3f7ivr6+ZGdn\nk52d7dU4Blt1dTV7c3Mp3JlLmGYlVoAvEg3IR1AnIXHaDGYuWUJaWtpVk/SuZu5IAgnApWP1K3Ek\nhp58G9jkhs8dcerq6ojU41JbQENDA1EREn0PZYUQxETrqDfVEGcMoaamxmtJABx1tj/5yT28+OK7\nnD5dTExMdo/r+2qahZqaAuAAa9YsYNGiBerLZpBZrVa2bNpEwYcfkG3U8WjsKAJ9rhyTotnsFJzI\nZ8v+fXwxcza33HEnoaGhXohYcZVHewcJIRYB9wPzeiu3du3ai69zcnLIyckZ1LiGC4vFgr+L33V2\nmx1nsxkbDDrs7VYC9JIOJw2bnpCUlMTatY+Rm7uLDRs+wGQKBmIwGsMQQofVeh6oQ4hq5s4dz4oV\nnm8DGIna2tp4Y93viSgq5KGk0QT49DzI0KDXkRUXy2S7nd1HDvCnoiLuevIpNcGbm+Xm5pKbm+uW\nfbmjTWA2jjr+ZV3vfwTIbhqHpwDvAcuklCW97E+1CfSgrKyM7b/5L+5PS3Ratr6+noqy3UybEtZj\nmaJTjfgGZnGwU0/Kmu8xbVr/GmYHg6ZplJeXU1VVRVVVA3a7JCwsgOTk0SQnJ6vufR5itVp5+bf/\nw5jSIpYkJ/b5ietEXT0b0bPmmf/w6pPm1c7bbQJ5wDghRDJQDawC7ri0gBAiCUcCWN1bAlB6Fx4e\nToPm2iLdEZERFBcF0NTcQWjIlQOxLBaNunod2ePiaThdx/QhNoTeYDCQmppKaqpr7QNXg5qaGkpK\nStA0jaioKNLT011anGYwbfvkE8KLClmSmtKvKrcJo6JoqjzL+6+9xponnlDdYoegAZ9hUkqbEOJR\nYDNfdhEtFEI85Pi1/BPwEyAC+INwnElWKWVv7QZKN0JCQpABwTR3WJxOE6ETOsanT6egcA/pqXYi\nIvwvXsTNLRZOFLWRlDIdg8FIrdXe52H4ivucO3eOV199m/z8SiAW0CNEE6GhnaxefQvXXDPdK3HV\n1taS/8E/+E7S6AG1ucxMiKPw2BEOHDhw1TecD0duGSfgTqo6qHf/fGc9kfu2MC/FtfVUzWYzpSXH\n0KxmAgMEFgto9kCSUyYSFxfPsbN17I8bz33ff2KQI1e609zczM9//jyNjfHExU3E0ePaobW1gbq6\nPTz66EpmzvT8l+eHf/sbQVs/ZmGy8+pHZ06bz/FxYBjf/ela1Yg/CLxdHaR4UPb8Bby77RNm2+wu\n9RKKiIggPGI+ra2tWCwWjAYjISEhF04a9jW2MeeeGzwQudKdjz/egskUQXLyldNmBAVFotMt5NVX\n3ycra4pHBzjZbDaObd/GI7Humas/JTwMe+kZqqurVSPxEKMq6IaZ+Ph4YmfNZUdZlcvbCATBQcFE\nRUZdNg/LwcoabKkT1SRag6C9vR2z2Uxzc3OPy0daLBa2bs0jLq7n1QkCAkJpbw/jyJEjgxVqt0wm\nE8FWC8FuSjxCCJJ0UFlZ6Zb9Ke6jngSGoZtuX8ULBUdJqTeTGtW/Bt3a5la2npfcd+/9qrHOTTRN\no7CwkB15OympOY0xwAdps6PX9MzLupY52bMv6yHT0NCAzeaPj09Ar/v18RlFWVklM2d6rhmtrq6O\nGDfX2sQYDNSUlYEH/w7FOZUEhqGgoCBuf+wHvPObZ1lpbyBtVN+63lU1NvN21TlufPQHamk+NzGZ\nTPzprT/THmYlcfYYrku/6eIU3a2NLRw/cIrtr+zk+qmLuOG6GxBCIITAbne+CI7dbvN4orZYLHS/\nPFH/+RkMdLa7b/0IxT3ULeAwlZyczB0//L98ZPNjQ3E5Fk1zuo1ms7O9tIK3znVy0+M/ZHJmpgci\nvfrV19fz/OvrCM+JY969i0iemHLZGg1BYcFMWTKNBY9ez46Kz/lgwz+RUhIdHU1QkKS9vanX/dts\ntaSne7arrMFgwPkZ1TdWuw2Dz9U1cdvVQCWBYSwxMZFHfvpf2OYv47niajYUl1Naf472TuvFMp2a\njXJzI1tKzvC/JyqozZzDQz/7JRMyMrwY+dVDSskr77xK3JJkxmb1/kXt6+/LnLsWsLdiP8eOHcNg\nMLBs2bVUV/c8lXZTUw3h4Z1kePj/Kzo6mjo3d9KrtXQyKtn57LeKZ6nqoEFgt9spKSlh2xf7KKyq\nwKrZCAsMZOHkKcyaMcOtc6n4+flxy+3fJGfZcg7m5bHj8EGqT5cgOi0IwGYwMio5meTs61gze44a\ntelmJSUlnDO0kDl1lkvlfXx9GL9kIlt3bCczM5MlSxaRn19EYeHnJCRMvdg+IKUdk6kMq/UoP/zh\n/R4fNBYTE4NZ6LBoGr5u+uwqdGSqnkFDjhon4GaNjY384a2/UqqX6DMzIDwUG3Y6m1toKzxF8Jmz\nrF6wmMULFw5af2kpJRaLBSklvr6+quF3EL369mu0ptkYf02ay9vY7Xa2/+5jnrj9e8THx9PR0cG/\n/rWJLVu+wGoNQQg9dnsTGRnxfPObN5Hspbvnd197lcS8z5g5euCT/ta0tLLeCk/86tfqfBwEapzA\nENHS0sKvX/0zlelj6EyIpKHFRKCuHYOPHvzAFppAeVwg/7npfSorK7n37rsHJQ4hRL8WkK+vr6ep\nyVE/HRISMqLXv3VVQelx5q9c2qdtdDodERmjKC0tJT4+Hj8/P77xja9x883LKS8vx2azERUV5fVG\n+5k5i/hgZy7TbDaMA1yHenetiRl336cSwBCkkoAbvbdpA4dCfbFHQuQojfGTUq446ePHxWBKDeX5\nZ19HJ+zcdcfdXr0wbDYbx44dI2/LxzSVniDKR4cQgnqLjeAxaWRft5zJkyd7fQ6bochms9GpWfHx\n63s/GmOAkfMdl/eU8fPzIz093V3hDVhKSgoJOUvYtns7N4zp/9NIYZ2J2tFJfH1er5MHK16irmw3\naWlp4c2tm9F/6wZSpyei03V/56TT6YkZm4B25/X8a8NHBIcGc+uKlV4ZSt/R0cH6l19CnshjXkwI\naVOT0OkccdjtklN1Z/n85d9yYNxU7njgOwQE9N6ffaTR6XTodXrsNhv6PiZJm9WGr3Ho95RZcdtt\nvHi8gFHVNUyL6/v8UtXNLWw838kdP3gQo7HnKagV71HPZm7y7rvv0JgawdjpY3tMAJeKzp5EZ0Qw\neeW7yc/P90CEl9M0jbdfepHo0gPcO2UME+KiLiYAAJ1OkBYbyT1ZY0ioPMpbf/wDVqu1lz16Vmtr\nK3l5eezcuZNDhw5hsVg8HoMQgsSY0dSW1fR526bT54bFWggBAQHc8+RT7PALJre8Apvd7vK2x2tN\nvGVu5ubHf0Bi4sDnH1IGh0oCbiClZOfBPURnjXW5ascnKAC70Ye0ucls37ulxy6Cg+XA/v0Yiw5w\nY0byZV/+XyWE4Ib0JILK8vli794ey3mKzWbjvX9s4PGn1vH/XqvklXet/O7PRTz+5HNs377L48dx\n4Yz5lOeV9mkbc00DxiYdaWmuNyZ7U1RUFA888xOqsq7h5VNlnDTVY7f3fJyrmpp5t7iU7aGR3PHM\nf3i8e6vSN6o6yA1Onz5Np+E8/gF9fLyXktHj4ynasZeqqipGj3ZtZlD4coqCvLwjFBWVYzI1IKUk\nPDyU8eOTmT59EllZWd1OOialJG/zRm5OjHSpGkoIwfzEKP7+6UaunTfPa7NASin565v/YOsejaS0\nxzEYv2z87mhv4pW31mO1aixdushjMWVOzuS9Le9jrq4nIi7KaXkpJSd3FpBzzfxh1UgaEhLC6u88\nzLF589mzcSObik6QpBfE6BwjgTW7nTrNTpWEzqhRzLj3W9x27bWqCmgYUEnADfYf+YK0GQkcrjgL\nTHVpm3bTOQL14BfgR+I1MRzMP+BSEpBSkp+fz+uv/4Nz53T4+iYQHDyJhATHSlsWy3ny8+vZt+9T\n/P3fZ9WqG5k3b+5lXzgVFRWIujMkTXW9sS8+LBi/8jOcPn2asWPHurydO1VWVrJ9TzUpEx5Bp7/8\n1PXzDyUx7S7efX8d116bTVBQkEdi8vHx4a6b7uDVt99gxuq5hEX3vJKblJLDnx4gojGYuSvneiQ+\ndxJCkJmZSWZmJiaTiaqqKmoqKznX1obBx4f4hARmxMcTHx8/rBLcSKeSgBucazGTnp3G8Q8PYWls\nwTfM+dKH5v3HWHhNMkIIQiKDMVc2ON1G0zTefPMdtm4tIDp6OsnJV955+vsH4+8fDIyhvr6Gn/70\nL4wa9QZ33bWSBQuuJSwsDLPZTLyfvk939EII4n0FZrPZa0lgz56DGAJmXJEALvDxDULTTeTQoSPM\nn++5L9lJEydxt3UVb762nrg5SYydNg6/QP+Lv5dSUltWTclnRYxqD2fNXffj080i7cNJdHS0owvx\nVNduepShSyUBN9BsGr7+QcyalsTOzXtIvG0popc7obbqenTHCpn08HUA6PQ6NFvvM7XYbDZefvkN\nPvvsLCkpS3ptfJZSUlxcyvHjdcC1VFeXUlz8L7KyDvHd7y5HpwNB3+vOdcIx0MlbKqrMBIZM6bWM\n0S+OmtpaD0X0palZU4mNiWXXvt3s+f02ApNDMAb5IDU7rWebCReh3Jx9A9OmTlNVJMqQopKAGwT4\nBdJx3sLMxZlU/3UHp97fQtyKhRj8Lq+Pl1LSfLqK5vc/4Zu3TCMwxNHlsuO8hUC/3qeEzs3dwZ49\n5YwZs+Cy1ae6U1dXR0GBmZCQGej1RqRMoanpC0ymKF54YRv33z+bxn7MDnbOCmM8VM3SncBAX7SG\n9l7L2KznCfD3zl12bGws37j1Nm5auoLS0lLa29sxGAxEzohk9OiBLdGoKINFJQE3mDAmg12Fmxkz\nMYlb717Izo0H2f/8azAxjYDUZHRGA5bGFjoPFxDW3sJdX5vG6HFfzqFSXVjHDek9V1/U19fz9tuf\nkJDgPAEAFBdX4es7Fr3ecccphCAkZDoVFVsJCJjKmTMmTMZgGlrPExnkWt//xvMdVAl/bh83zqXy\ng2HOrAy+OHKY6NjuF2GRUmK3HGHKlK95OLLL+fv7M2nSJK/GoCiuUknADaZmTePD3H/Q3taBf6Af\ni2/JZvbidk4cLOHM0YNomp2QQB8yFqcyelz8ZXeELY2ttJ7pJPO2nqd13rXrM+z2OHx9A12Kx2xu\nJSjo8gZKnc6I0ZiGyVRBSUkoc5YsY9+Wd7gxI8WlfeadqSVr8de8Wpc9efJkYiNyqas+yqi4y4+X\nlJLK0zvInBDYp15WijLSqSTgBv7+/mRPnMPxvUVcs8RRZx0Q5M/0BZOZ7mTb458VcW3W/B7rie12\nO1u2fE5MjOtD7n18DNhsneh0/pf9PDAwmZqavRgM05k1dx4v7dpGYlUdmQm9z1FTeNZEvj6cby9Y\n6HIMg8FoNPLEY3fym/99k7KTxYRFTcfXL4TzbfU0N+QxPrGJhx64R1W7KEofqH5cbnJdzvU0Humg\n9GiZy9ucPHCKjmJBzvye+7WbTCba23UuPwUAjBkzira27tYg1mG1WklLiyY4OJi7HnuSze3+bDtZ\nTpul84rS5y1WdhSfYUOLkTsee9KtU2D3V0xMDP/504d54M44ov02I1veICliN48/OJGnn3rAY11D\nFeVqoaaSdqPa2lpe/Os6YmeHkjErDYOh+x48mlXj2J4TmA918PA9j/Y6x//Ro0d57rmPSEyc43Ic\nFouFXbsO0tYWS1DQaPR6I5rWQUtLGUbjEZ599t+4/npHz6SmpiZyP9lE4e7tjDNYiDY42hDqrZIi\nqw8T5i1i4dJlhIeH9+1gKIriMQOZStotSUAIsQx4DseTxctSyl91U+Z3wHKgDbhPSnm4h30N2yQA\njvUE3vvobxSdLSRhWhRjpyQREOyolmlrbqf0cDnV+WYmJmXytRX/RnBw72MKDh48yLp1n5KU5Nqi\nJRd0dHRQVHSa8vJ67HY9BoOd1NQYfH3bWLVqAsuXL7usfHt7OwUFBTSZzQCEhIczadIkNWmcogwD\nXl1PQDi6q6wDlgBngTwhxD+llCcuKbMcSJVSjhdCzAJeBGYP9LOHorCwML519wOYzWb27d/LoXcO\n0NbeCkBQQDDXTJzJvQ/OJCys55Gll/Lx8UEI54uRf5Wfnx9TpmQwaZINTdMwGo3odDrKyg7i53fl\nVBL+/v7MmDGjz5+jKMrw5o6G4ZlAsZSyHEAIsR64FThxSZlbgb8ASCn3CSFChRAxUkrPj+rxkIiI\nCJYvvZHlS28c0H5GjRqFlK393l6v11+26LlO10ZsbN+nBFYU5erkjobhBKDikveVXT/rrUxVN2WU\nbkRFRREQAB0d/U8EF9hsGtCsulAqinLRkOwiunbt2ouvc3JyyMnJ8Vos3qbT6Vi6dC4ffFBEUtK0\nAe3LZCojOzvDaTuEoihDW25uLrm5uW7Z14AbhoUQs4G1UsplXe9/BMhLG4eFEC8C26WU73S9PwEs\n7K46aLg3DA+Gc+fO8fTTvyI8fG7X5HB9p2mdVFRsZe3a7zBmzBg3R6goijcNpGHYHdVBecA4IUSy\nEMIHWAV8+JUyHwL3wMWk0Xg1twe4W3h4OHffvYLq6jzs9r43EkspqajYz4oVM1UCUBTlMgNOAlJK\nG/AosBkoANZLKQuFEA8JIR7sKrMROC2EOAX8EXhkoJ870sybN5fFi9MoK9uNzeb6Mo9S2jlzZj+T\nJwezcuXNgxihoijDkRosNozYbDb+9rf32bhxP+HhWYSHx/VavrXVTF3dQWbPTmHNmrvx8/Prtbyi\nKMOT1weLuZNKAs6dPHmS11//O2fPdmAwxBESEt3VViCwWNpoaanHYqkmLEzjzjtvITt7hppPR1Gu\nYioJjEB2u51Tp05x6NBRTp4so66uASkhPDyE9PQUsrImMnHiRAyGIdkBTFEUN1JJQFEUZQTzdu8g\nRVEUZZhSSUBRFGUEU0lAURRlBFNJQFEUZQRTSUBRFGUEU0lAURRlBFNJQFEUZQRTSUBRFGUEU0lA\nURRlBFNJQFEUZQRTSUBRFGUEU0lAURRlBFNJQFEUZQRTSUBRFGUEU0lAURRlBFNJQFEUZQRTSUBR\nFGUEU0lAURRlBFNJQFEUZQQbUBIQQoQLITYLIU4KIT4RQoR2U2a0EGKbEKJACHFUCPHYQD5TURRF\ncZ+BPgn8CNgipUwHtgE/7qaMBvxASjkJmAN8VwgxYYCfOyTl5uZ6O4QBUfF7l4rfu4Z7/P010CRw\nK/B61+vXgZVfLSClrJFSHu563QoUAgkD/NwhabifRCp+71Lxe9dwj7+/BpoERkkpa8HxZQ+M6q2w\nECIFmArsG+DnKoqiKG5gcFZACPEpEHPpjwAJPNNNcdnLfoKAvwPf73oiUBRFUbxMSNnj97bzjYUo\nBHKklLVCiFhgu5Qyo5tyBuAjYJOU8nkn++x/QIqiKCOUlFL0ZzunTwJOfAjcB/wKuBf4Zw/lXgGO\nO0sA0P8/RFEURem7gT4JRADvAolAOXC7lLJRCBEHvCSlvEkIMRfYCRzFUV0kgf8jpfx4wNEriqIo\nAzKgJKAoiqIMb14dMTxcB5sJIZYJIU4IIYqEEE/3UOZ3QohiIcRhIcRUT8fYG2fxCyHuFEIc6fq3\nWwiR6Y04e+LK8e8qly2EsAohvu7J+Jxx8fzJEUIcEkIcE0Js93SMPXHh3AkRQnzYdd4fFULc54Uw\neySEeFkIUSuEyO+lzFC+dnuNv1/XrpTSa/9wtCX8sOv108B/d1MmFpja9ToIOAlM8GLMOuAUkAwY\ngcNfjQdYDmzoej0L2OvN49yP+GcDoV2vlw23+C8ptxVHh4SvezvuPh7/UKAASOh6H+XtuPsQ+4+B\nZy/EDTQABm/Hfkl883B0U8/v4fdD9tp1Mf4+X7venjtoOA42mwkUSynLpZRWYD2Ov+NStwJ/AZBS\n7gNChRAxDA1O45dS7pVSNnW93cvQGtznyvEH+B6OLsl1ngzOBa7EfyfwnpSyCkBKWe/hGHviSuwS\nCO56HQw0SCk1D8bYKynlbuBcL0WG8rXrNP7+XLveTgLDcbBZAlBxyftKrjzQXy1T1U0Zb3El/kt9\nG9g0qBH1jdP4hRDxwEop5Qs4xrUMJa4c/zQgQgixXQiRJ4RY7bHoeudK7OuAiUKIs8AR4Pseis1d\nhvK121cuXbsD7SLqlBpsNnwJIRYB9+N4BB1OnsNRvXjBUEsEzhiA6cBiIBD4XAjxuZTylHfDcskN\nwCEp5WIhRCrwqRBiirpmPasv1+6gJwEp5fU9/a6rgSNGfjnYrNtH967BZn8H3pBS9jQWwVOqgKRL\n3o/u+tlXyyQ6KeMtrsSPEGIK8CdgmZSyt8dnT3Ml/hnAeiGEwFEvvVwIYZVSfuihGHvjSvyVQL2U\nsgPoEELsBLJw1Md7kyux3w88CyClLBFCnAYmAPs9EuHADeVr1yV9vXa9XR10YbAZuGmwmQfkAeOE\nEMlCCB9gFY6/41IfAvcACCFmA40Xqr2GAKfxCyGSgPeA1VLKEi/E2Bun8Uspx3b9G4Pj5uGRIZIA\nwLXz55/APCGEXggRgKOBstDDcXbHldjLgesAuurS04BSj0bpnKDnp8OhfO1e0GP8/bp2vdzSHQFs\nwdHjZzMQ1vXzOOCjrtdzARuOngiHgIM4Mpw3417WFXMx8KOunz0EPHhJmXU47tyOANO9GW9f4wde\nwtGr42DXMf/C2zH39fhfUvYVhlDvoD6cP0/i6CGUD3zP2zH34dyJAz7pijsfuMPbMX8l/reAs4AF\nOIPjyWU4Xbu9xt+fa1cNFlMURRnBvF0dpCiKoniRSgKKoigjmEoCiqIoI5hKAoqiKCOYSgKKoigj\nmEoCiqIoI5hKAoqiKCOYSgKKoigj2P8HBuOPsAytenIAAAAASUVORK5CYII=\n", 72 | "text/plain": [ 73 | "" 74 | ] 75 | }, 76 | "metadata": {}, 77 | "output_type": "display_data" 78 | } 79 | ], 80 | "source": [ 81 | "import numpy as np\n", 82 | "import matplotlib.pyplot as plt\n", 83 | "\n", 84 | "N = 50\n", 85 | "N = min(N, 1000) # Prevent generation of too many numbers :)\n", 86 | "x = np.random.rand(N)\n", 87 | "y = np.random.rand(N)\n", 88 | "colors = np.random.rand(N)\n", 89 | "area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radii\n", 90 | "\n", 91 | "plt.scatter(x, y, s=area, c=colors, alpha=0.5)" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "metadata": {}, 97 | "source": [ 98 | "We are currently working on supporting [Jupyter's magic commands](http://ipython.readthedocs.io/en/stable/interactive/magics.html) in Stencila via a bridge to Jupyter kernels." 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "# Metadata\n", 106 | "\n", 107 | "To add some metadata about the document (such as authors, title, abstract and so on), In Jupyter, select `Edit -> Edit Notebook metadata` from the top menu. Add the title and abstract as JSON strings and authors and organisations metadata as [JSON arrays](https://www.w3schools.com/js/js_json_arrays.asp). Author `affiliation` identifiers (like `university-of-earth` below) must be unique and preferably use only lowercase characters and no spaces.\n", 108 | " \n", 109 | "For example," 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": {}, 115 | "source": [ 116 | "```\n", 117 | " \"authors\": [\n", 118 | " {\n", 119 | " \"given-names\": \"Your first name goes here\",\n", 120 | " \"surname\": \"Your last name goes here\",\n", 121 | " \"email\": \"your.email@your-organisation\",\n", 122 | " \"corresponding\": \"yes / no\",\n", 123 | " \"affiliation\": \"university-of-earth\"\n", 124 | " }\n", 125 | " ],\n", 126 | " \n", 127 | " \"organisations\": [ \n", 128 | " {\n", 129 | " \"university-of-earth\": {\n", 130 | " \"institution\": \"Your organisation name\",\n", 131 | " \"city\": \"Your city\",\n", 132 | " \"country\": \"Your country\" \n", 133 | " }\n", 134 | " ],\n", 135 | "\n", 136 | " \"title\": \"Your title goes here\",\n", 137 | " \"abstract\": \"This is a paper about lots of different interesting things\",\n", 138 | " \n", 139 | " ```\n", 140 | " \n" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "# Citations and references \n", 148 | "\n", 149 | "Stencila supports Pandoc style citations and reference lists within Jupyter notebook Markdown cells. Add a `bibliography` entry to the notebook's metadata which points to a file containing your list of references e.g.\n", 150 | "\n", 151 | "```json\n", 152 | "\"bibliography\": \"my-bibliography.bibtex\"\n", 153 | "```\n", 154 | "\n", 155 | "Then, within Markdown cells, you can insert citations inside square brackets and separated by semicolons. Each citation is represented using the `@` symbol followed by the citation identifier from the bibliography database e.g.\n", 156 | "\n", 157 | "```json\n", 158 | "[@perez2015project; @kluyver2016jupyter]\n", 159 | "```\n", 160 | "\n", 161 | "The [cite2c](https://github.com/takluyver/cite2c) Jupyter extension allows for easier, \"cite-while-you-write\" insertion of citations from a Zotero library. We're hoping to support conversion of cite2cstyle citations/references in the [future](https://github.com/stencila/convert/issues/14).\n" 162 | ] 163 | } 164 | ], 165 | "metadata": { 166 | "anaconda-cloud": {}, 167 | "authors": [ 168 | { 169 | "given-names": "Aleksandra", 170 | "surname": "Pawlik" 171 | } 172 | ], 173 | "bibliography": "bibliography.bibtex", 174 | "kernelspec": { 175 | "display_name": "Python 3", 176 | "language": "python", 177 | "name": "python3" 178 | }, 179 | "language_info": { 180 | "codemirror_mode": { 181 | "name": "ipython", 182 | "version": 3 183 | }, 184 | "file_extension": ".py", 185 | "mimetype": "text/x-python", 186 | "name": "python", 187 | "nbconvert_exporter": "python", 188 | "pygments_lexer": "ipython3", 189 | "version": "3.5.2" 190 | }, 191 | "title": "Jupyter and Stencila" 192 | }, 193 | "nbformat": 4, 194 | "nbformat_minor": 2 195 | } 196 | -------------------------------------------------------------------------------- /archive/py-jupyter/py-jupyter.ipynb.jats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | Jupyter and Stencila 8 | 9 | 10 | 11 | 12 | Pawlik 13 | Aleksandra 14 | 15 | 16 | 17 | 18 |

An example of a Jupyter notebook converted into a JATS document for editing in Stencila.

19 |
20 |
21 |
22 | 23 | 24 | 25 | Introduction 26 |

Jupyter notebooks (13) are one of the most popular platforms for doing reproducible research. Stencila supports importing of Jupyter Notebook .ipynb files. This allows you to work with collegues to refine a document for final publication while still retaining the code cells, and thus reprodubility of your the work. In the future we also plan to support exporting to .ipynb files.

27 |
28 | 29 | Markdown cells 30 |

Most standard Markdown should be supported by the importer including inline code, headings etc (although the Stencila user interface do not currently support rendering of some elements e.g. math and lists).

31 |
32 | 33 | Code cells 34 |

Code cells in notebooks are imported without loss. Stencila’s user interface currently differs from Jupyter in that code cells are executed on update while you are typing. This produces a very reactive user experience but is inappropriate for more compute intensive, longer running code cells. We are currently working on improving this to allowing users to decide to execute cells explicitly (e.g. using Ctrl+Enter).

35 | 36 | import sys 37 | import time 38 | 'Hello this is Python %s.%s and it is %s' % (sys.version_info[0], sys.version_info[1], time.strftime('%c')) 39 | {} 40 | 41 | 42 | 43 |

Stencila also support Jupyter code cells that produce plots. The cell below produces a simple plot based on the example from the Matplotlib website. Try changing the code below (for example, the variable N).

44 | 45 | import numpy as np 46 | import matplotlib.pyplot as plt 47 | 48 | N = 50 49 | N = min(N, 1000) # Prevent generation of too many numbers :) 50 | x = np.random.rand(N) 51 | y = np.random.rand(N) 52 | colors = np.random.rand(N) 53 | area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radii 54 | 55 | plt.scatter(x, y, s=area, c=colors, alpha=0.5) 56 | plt.show() 57 | {} 58 | 59 | 60 | 61 |

We are currently working on supporting Jupyter’s magic commands in Stencila via a bridge to Jupyter kernels.

62 |
63 | 64 | Metadata 65 |

To add some metadata about the document (such as authors, title, abstract and so on), In Jupyter, select Edit -> Edit Notebook metadata from the top menu. Add the title and abstract as JSON strings and authors and organisations metadata as JSON arrays. Author affiliation identifiers (like university-of-earth below) must be unique and preferably use only lowercase characters and no spaces.

66 |

For example,

67 | "authors": [ 68 | { 69 | "given-names": "Your first name goes here", 70 | "surname": "Your last name goes here", 71 | "email": "your.email@your-organisation", 72 | "corresponding": "yes / no", 73 | "affiliation": "university-of-earth" 74 | } 75 | ], 76 | 77 | "organisations": [ 78 | { 79 | "university-of-earth": { 80 | "institution": "Your organisation name", 81 | "city": "Your city", 82 | "country": "Your country" 83 | } 84 | ], 85 | 86 | "title": "Your title goes here", 87 | "abstract": "This is a paper about lots of different interesting things", 88 | 89 |
90 | 91 | Citations and references 92 |

Stencila supports Pandoc style citations and reference lists within Jupyter notebook Markdown cells. Add a bibliography entry to the notebook’s metadata which points to a file containing your list of references e.g.

93 | "bibliography": "my-bibliography.bibtex" 94 |

Then, within Markdown cells, you can insert citations inside square brackets and separated by semicolons. Each citation is represented using the @ symbol followed by the citation identifier from the bibliography database e.g.

95 | [@perez2015project; @kluyver2016jupyter] 96 |

The cite2c Jupyter extension allows for easier, “cite-while-you-write” insertion of citations from a Zotero library. We’re hoping to support conversion of cite2cstyle citations/references in the future.

97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Perez 106 | Fernando 107 | 108 | 109 | Granger 110 | Brian E 111 | 112 | 113 | Project jupyter: Computational narratives as the engine of collaborative data science 114 | Retrieved September 115 | 2015 116 | 11 117 | 207 118 | 119 | 120 | 121 | 122 | 123 | 124 | Kluyver 125 | Thomas 126 | 127 | 128 | Ragan-Kelley 129 | Benjamin 130 | 131 | 132 | Pérez 133 | Fernando 134 | 135 | 136 | Granger 137 | Brian E 138 | 139 | 140 | Bussonnier 141 | Matthias 142 | 143 | 144 | Frederic 145 | Jonathan 146 | 147 | 148 | Kelley 149 | Kyle 150 | 151 | 152 | Hamrick 153 | Jessica B 154 | 155 | 156 | Grout 157 | Jason 158 | 159 | 160 | Corlay 161 | Sylvain 162 | 163 | 164 | Others 165 | 166 | 167 | Jupyter notebooks-a publishing format for reproducible computational workflows. 168 | ELPUB 169 | 2016 170 | 87 171 | 172 | 173 | 174 | 175 | 176 | 177 | Ragan-Kelley 178 | M 179 | 180 | 181 | Perez 182 | F 183 | 184 | 185 | Granger 186 | B 187 | 188 | 189 | Kluyver 190 | T 191 | 192 | 193 | Ivanov 194 | P 195 | 196 | 197 | Frederic 198 | J 199 | 200 | 201 | Bussonnier 202 | M 203 | 204 | 205 | The jupyter/ipython architecture: A unified view of computational research, from interactive exploration to communication and publication. 206 | AGU Fall Meeting Abstracts 207 | 2014 208 | 209 | 210 | 211 | 212 |
-------------------------------------------------------------------------------- /archive/r-markdown/bibliography.bibtex: -------------------------------------------------------------------------------- 1 | @article{baumer2014r, 2 | title={R Markdown: Integrating a reproducible analysis tool into introductory statistics}, 3 | author={Baumer, Ben and Cetinkaya-Rundel, Mine and Bray, Andrew and Loi, Linda and Horton, Nicholas J}, 4 | journal={arXiv preprint arXiv:1402.1894}, 5 | year={2014} 6 | } 7 | 8 | @book{xie2016bookdown, 9 | title={Bookdown: Authoring Books and Technical Documents with R Markdown}, 10 | author={Xie, Yihui}, 11 | year={2016}, 12 | publisher={CRC Press} 13 | } 14 | -------------------------------------------------------------------------------- /archive/r-markdown/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /archive/r-markdown/rmarkdown.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: RMarkdown and Stencila 3 | author: 4 | - surname: Bentley 5 | given-names: Nokome 6 | affiliation: stencila 7 | - surname: Pawlik 8 | given-names: Aleksandra 9 | - surname: Aufrieter 10 | given-names: Michael 11 | affiliation: substance 12 | - surname: Buchtala 13 | given-names: Oliver 14 | affiliation: substance 15 | organisations: 16 | - id: stencila 17 | name: Stencila 18 | city: Kaikoura 19 | country: New Zealand 20 | - id: substance 21 | name: Substance GmbH 22 | city: Linz 23 | country: Austria 24 | abstract: | 25 | Stencila currently supports import of RMarkdown documents. This allows use cases like allowing collaborators to work on the same document using a WYSIWYG editing environment, or for you to put the final touches on a paper before final submission to a journal. 26 | bibliography: bibliography.bibtex 27 | --- 28 | 29 | # Introduction 30 | 31 | [RMarkdown](https://rmarkdown.rstudio.com/) is a popular format for reproducible research using the R programming language ([@baumer2014r],[@xie2016bookdown]). Stencila is a able to import RMarkdown documents. For example, the source for this example document is available on [Github](https://github.com/stencila/stencila/blob/more-examples/data/r-markdown/rmarkdown.Rmd). Eventually, we'll also support exporting to RMarkdown, allowing WYSIWYG editing of RMarkdown documents in Stencila. 32 | 33 | # Code chunks 34 | 35 | In RMarkdown, code "chunks" are written using "fenced" code blocks. Stencila converts these code "chunks" into Stencila code "cells" like this one: 36 | 37 | ```{r} 38 | n <- 500 39 | ``` 40 | 41 | Stencila cells behave like functions having zero or more named `inputs` (like function arguments) and an optional `output` (like a assigning a function's return value). A cell's inputs and outputs are determined by analyzing the cell's code. The cell above has no inputs but produces an output variable named `n`. Stencila's execution engine uses this information to make code cells reactive. For instance, the cell below has `n` as an input i.e it is dependent on the first cell. So when you update the `n` in the cell above, the following plot will update. 42 | 43 | ```{r} 44 | s <- min(1000, n) 45 | x <- runif(s) 46 | y <- x + runif(s) 47 | z <- y + rnorm(s) 48 | plot(x, y, cex=z*2, col=rainbow(length(z), alpha=0.5)[rank(x+z)], pch=16) 49 | ``` 50 | 51 | # Figures 52 | 53 | In RMarkdown, code chunks can have various options. A common use for options is to set the caption and dimensions of figures. Stencila converts code chunks with the `fig.cap` option into JATS `` elements with a ``. This allows the user to edit the figure caption and for automatic figure numbering and referencing (although that is not working 100% right now). Other options are placed in a comment at the top of the cell so that they are preserved (and eventually will be used to apply those options within the R execution context). 54 | 55 | ```{r fig.width=7,fig.height=6,fig.cap="Figure title"} 56 | hist(z, breaks=40, col=hsv(0.6, 0.9, 1), xlab="Value", main="") 57 | ``` 58 | -------------------------------------------------------------------------------- /archive/r-markdown/rmarkdown.Rmd.jats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | RMarkdown and Stencila 8 | 9 | 10 | 11 | 12 | Bentley 13 | Nokome 14 | 15 | 16 | 17 | 18 | 19 | Pawlik 20 | Aleksandra 21 | 22 | 23 | 24 | 25 | Aufrieter 26 | Michael 27 | 28 | 29 | 30 | 31 | 32 | Buchtala 33 | Oliver 34 | 35 | 36 | 37 | 38 | 39 | Stencila 40 | Kaikoura 41 | New Zealand 42 | 43 | 44 | Substance GmbH 45 | Linz 46 | Austria 47 | 48 | 49 |

Stencila currently supports import of RMarkdown documents. This allows use cases like allowing collaborators to work on the same document using a WYSIWYG editing environment, or for you to put the final touches on a paper before final submission to a journal.

50 |
51 |
52 |
53 | 54 | 55 | 56 | Introduction 57 |

RMarkdown is a popular format for reproducible research using the R programming language (1,2). Stencila is a able to import RMarkdown documents. For example, the source for this example document is available on Github. Eventually, we’ll also support exporting to RMarkdown, allowing WYSIWYG editing of RMarkdown documents in Stencila.

58 |
59 | 60 | Code chunks 61 |

In RMarkdown, code “chunks” are written using “fenced” code blocks. Stencila converts these code “chunks” into Stencila code “cells” like this one:

62 | 63 | n <- 500 64 | {} 65 | 66 | 67 | 68 |

Stencila cells behave like functions having zero or more named inputs (like function arguments) and an optional output (like a assigning a function’s return value). A cell’s inputs and outputs are determined by analyzing the cell’s code. The cell above has no inputs but produces an output variable named n. Stencila’s execution engine uses this information to make code cells reactive. For instance, the cell below has n as an input i.e it is dependent on the first cell. So when you update the n in the cell above, the following plot will update.

69 | 70 | s <- min(1000, n) 71 | x <- runif(s) 72 | y <- x + runif(s) 73 | z <- y + rnorm(s) 74 | plot(x, y, cex=z*2, col=rainbow(length(z), alpha=0.5)[rank(x+z)], pch=16) 75 | {} 76 | 77 | 78 | 79 |
80 | 81 | Figures 82 |

In RMarkdown, code chunks can have various options. A common use for options is to set the caption and dimensions of figures. Stencila converts code chunks with the fig.cap option into JATS <fig fig-type="repro-fig"> elements with a <caption>. This allows the user to edit the figure caption and for automatic figure numbering and referencing (although that is not working 100% right now). Other options are placed in a comment at the top of the cell so that they are preserved (and eventually will be used to apply those options within the R execution context).

83 | 84 | 85 | Figure title 86 | 87 | 88 | #: fig.width=7,fig.height=6 89 | hist(z, breaks=40, col=hsv(0.6, 0.9, 1), xlab="Value", main="") 90 | {} 91 | 92 | 93 |
94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Baumer 102 | Ben 103 | 104 | 105 | Cetinkaya-Rundel 106 | Mine 107 | 108 | 109 | Bray 110 | Andrew 111 | 112 | 113 | Loi 114 | Linda 115 | 116 | 117 | Horton 118 | Nicholas J 119 | 120 | 121 | R markdown: Integrating a reproducible analysis tool into introductory statistics 122 | arXiv preprint arXiv:1402.1894 123 | 2014 124 | 125 | 126 | 127 | 128 | 129 | 130 | Xie 131 | Yihui 132 | 133 | 134 | Bookdown: Authoring books and technical documents with r markdown 135 | CRC Press 136 | 2016 137 | 138 | 139 | 140 | 141 |
-------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Stencila Project 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /nbstencilaproxy/__init__.py: -------------------------------------------------------------------------------- 1 | from nbstencilaproxy.handlers import setup_handlers 2 | 3 | # Jupyter Extension points 4 | def _jupyter_server_extension_paths(): 5 | return [{ 6 | 'module': 'nbstencilaproxy', 7 | }] 8 | 9 | def _jupyter_nbextension_paths(): 10 | return [{ 11 | "section": "tree", 12 | "dest": "nbstencilaproxy", 13 | "src": "static", 14 | "require": "nbstencilaproxy/tree" 15 | }] 16 | 17 | def load_jupyter_server_extension(nbapp): 18 | setup_handlers(nbapp) 19 | -------------------------------------------------------------------------------- /nbstencilaproxy/handlers.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from tornado import web 4 | from urllib.parse import urlunparse, urlparse 5 | 6 | from notebook.base.handlers import IPythonHandler 7 | 8 | from nbserverproxy.handlers import SuperviseAndProxyHandler 9 | 10 | here = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | 13 | class AddSlashHandler(IPythonHandler): 14 | """Handler for adding trailing slash to URLs that need them""" 15 | 16 | @web.authenticated 17 | def get(self, *args): 18 | src = urlparse(self.request.uri) 19 | dest = src._replace(path=src.path + "/") 20 | self.redirect(urlunparse(dest)) 21 | 22 | 23 | def _find_stencila_js(name): 24 | """Find a stencila.js file in the bundled stencila npm package""" 25 | return os.path.join(here, "node_modules", "nbstencilaproxy", name) 26 | 27 | 28 | # define our proxy handler for proxying the application 29 | 30 | 31 | class StencilaProxyHandler(SuperviseAndProxyHandler): 32 | 33 | name = "stencila" 34 | 35 | def get_env(self): 36 | return {"STENCILA_PORT": str(self.port), "BASE_URL": self.state["base_url"]} 37 | 38 | def get_cmd(self): 39 | return ["node", _find_stencila_js("stencila.js")] 40 | 41 | 42 | class StencilaHostProxyHandler(SuperviseAndProxyHandler): 43 | 44 | name = "stencila-host" 45 | 46 | def get_env(self): 47 | return {"STENCILA_HOST_PORT": str(self.port)} 48 | 49 | def get_cmd(self): 50 | return ["node", _find_stencila_js("stencila-host.js")] 51 | 52 | def proxy_request_options(self): 53 | """Increase the request timeout to avoid 599 errors when executing 54 | long running cells""" 55 | options = super().proxy_request_options() 56 | options.update(dict( 57 | request_timeout=3600 58 | )) 59 | return options 60 | 61 | 62 | def setup_handlers(app): 63 | app.log.info("Enabling stencila proxy") 64 | app.web_app.add_handlers( 65 | ".*", 66 | [ 67 | ( 68 | app.base_url + "stencila/(.*)", 69 | StencilaProxyHandler, 70 | dict(state=dict(base_url=app.base_url, notebook_dir=app.notebook_dir)), 71 | ), 72 | ( 73 | app.base_url + "stencila-host/(.*)", 74 | StencilaHostProxyHandler, 75 | dict(state=dict(base_url=app.base_url, notebook_dir=app.notebook_dir)), 76 | ), 77 | (app.base_url + "stencila", AddSlashHandler), 78 | (app.base_url + "stencila-host", AddSlashHandler), 79 | ], 80 | ) 81 | -------------------------------------------------------------------------------- /nbstencilaproxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbstencilaproxy-py", 3 | "version": "0.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /nbstencilaproxy/static/tree.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | var $ = require('jquery'); 3 | var Jupyter = require('base/js/namespace'); 4 | var utils = require('base/js/utils'); 5 | 6 | var base_url = utils.get_body_data('baseUrl'); 7 | 8 | 9 | function load() { 10 | if (!Jupyter.notebook_list) return; 11 | 12 | /* locate the right-side dropdown menu of apps and notebooks */ 13 | var menu = $('.tree-buttons').find('.dropdown-menu'); 14 | 15 | /* create a divider */ 16 | var divider = $('
  • ') 17 | .attr('role', 'presentation') 18 | .addClass('divider'); 19 | 20 | /* add the divider */ 21 | menu.append(divider); 22 | 23 | /* create our list item */ 24 | var stencilasession_item = $('
  • ') 25 | .attr('role', 'presentation') 26 | .addClass('new-stencila'); 27 | 28 | /* create our list item's link */ 29 | var stencilasession_link = $('') 30 | .attr('role', 'menuitem') 31 | .attr('tabindex', '-1') 32 | .attr('href', base_url + 'stencila/') 33 | .attr('target', '_blank') 34 | .text('Stencila Session'); 35 | 36 | /* add the link to the item and 37 | * the item to the menu */ 38 | stencilasession_item.append(stencilasession_link); 39 | menu.append(stencilasession_item); 40 | } 41 | 42 | return { 43 | load_ipython_extension: load 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /new-session-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minrk/nbstencilaproxy/74adbb3669f6ee7ccca1c54cc77fd882eae800ae/new-session-button.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbstencilaproxy", 3 | "version": "0.0.1", 4 | "description": "javascript dependencies for nbstencilaproxy", 5 | "main": "stencila.js", 6 | "dependencies": { 7 | "morgan": "^1.9.1", 8 | "mustache": "^2.3.0", 9 | "stencila": "0.28.2", 10 | "stencila-node": "0.28.15" 11 | }, 12 | "devDependencies": {}, 13 | "files": [ 14 | "*.js", 15 | "index.html" 16 | ], 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/minrk/nbstencilaproxy.git" 23 | }, 24 | "keywords": [ 25 | "jupyter", 26 | "dar", 27 | "stencila", 28 | "texture" 29 | ], 30 | "author": "Min RK, Daniel Nüst", 31 | "license": "BSD-3-Clause", 32 | "bugs": { 33 | "url": "https://github.com/minrk/nbstencilaproxy/issues" 34 | }, 35 | "homepage": "https://github.com/minrk/nbstencilaproxy#readme" 36 | } 37 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.command.build_py import build_py 2 | import glob 3 | import os 4 | from subprocess import check_call 5 | import tempfile 6 | 7 | import setuptools 8 | 9 | here = os.path.dirname(os.path.abspath(__file__)) 10 | name = "nbstencilaproxy" 11 | pkg = os.path.join(here, name) 12 | 13 | 14 | def npm_install(): 15 | """Install nbstencilaproxy js package""" 16 | with tempfile.TemporaryDirectory() as td: 17 | check_call(["npm", "pack", here], cwd=td) 18 | tgz = glob.glob(os.path.join(td, "*.tgz"))[0] 19 | check_call(["npm", "install", "--no-save", tgz], cwd=pkg) 20 | 21 | 22 | def find_package_data(): 23 | patterns = ["static/**"] 24 | package_data = {"nbstencilaproxy": patterns} 25 | for parent, dirs, files in os.walk(os.path.join(pkg, "node_modules")): 26 | parent = parent[len(pkg) + 1 :] 27 | # exclude utterly massive and apparently unused '@stdlib' package 28 | if "@stdlib" in parent: 29 | continue 30 | for d in dirs: 31 | if "@stdlib" in d: 32 | continue 33 | patterns.append("{}/{}/**".format(parent, d)) 34 | return package_data 35 | 36 | 37 | class build_npm_py(build_py): 38 | """install with npm packages""" 39 | 40 | def run(self): 41 | # when installing, install npm package 42 | npm_install() 43 | self.distribution.package_data = find_package_data() 44 | # re-run finalize to get package_data 45 | self.finalize_options() 46 | return super().run() 47 | 48 | 49 | setuptools.setup( 50 | name=name, 51 | version="0.1.1", 52 | url="https://github.com/minrk/" + name, 53 | author="Min RK, Daniel Nüst, Ryan Lovett", 54 | description="Jupyter extension to proxy Stencila", 55 | long_description=open("README.md", encoding="utf-8").read(), 56 | long_description_content_type="text/markdown", 57 | packages=setuptools.find_packages(), 58 | cmdclass={"build_py": build_npm_py}, 59 | keywords=["Jupyter"], 60 | classifiers=["Framework :: Jupyter"], 61 | install_requires=["notebook", "nbserverproxy >= 0.8.5"], 62 | package_data={"nbstencilaproxy": ["static/**", "node_modules/**"]}, 63 | ) 64 | -------------------------------------------------------------------------------- /stencila-host.js: -------------------------------------------------------------------------------- 1 | const stencila = require("stencila-node"); 2 | 3 | // Get the port passed here from the `nbserverproxy.hanglers.SuperviseAndProxyHandler` 4 | const port = parseInt(process.env.STENCILA_HOST_PORT || '2000') 5 | 6 | // Run the Stencila execution host without 7 | // any authentication (handled by Jupyter) 8 | process.env.STENCILA_AUTH = 'false' 9 | 10 | stencila.run({ port }) 11 | -------------------------------------------------------------------------------- /stencila.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const fs = require("fs"); 3 | const darServer = require("dar-server"); 4 | const express = require("express"); 5 | const logging = require("morgan"); 6 | const mustache = require("mustache"); 7 | 8 | const port = parseInt(process.env.STENCILA_PORT || "4000"); 9 | const archiveDir = process.env.STENCILA_ARCHIVE_DIR || process.env.HOME; 10 | var archive = process.env.STENCILA_ARCHIVE; 11 | if (!archive) { 12 | for (var name of fs.readdirSync(archiveDir)) { 13 | if (fs.lstatSync(path.join(archiveDir, name)).isDirectory()) { 14 | archive = name; 15 | break; 16 | } 17 | } 18 | } 19 | archive = archive || "manuscript"; 20 | const baseUrl = process.env.BASE_URL || "/"; 21 | const serverUrl = baseUrl + "stencila"; 22 | const server = express(); 23 | 24 | darServer.serve(server, { 25 | port: port, 26 | serverUrl: serverUrl, 27 | rootDir: archiveDir, 28 | apiUrl: "/archives" 29 | }); 30 | 31 | // check for local node_modules 32 | let node_modules = path.join(__dirname, "node_modules"); 33 | if (!fs.existsSync(node_modules)) { 34 | node_modules = path.dirname(path.resolve(__dirname)); 35 | } 36 | let stencilaDist = path.join(node_modules, "stencila", "dist"); 37 | 38 | console.log("Stencila app root: %s", stencilaDist); 39 | console.log("DAR archive path: %s", archiveDir); 40 | console.log("DAR public URL: %s", serverUrl); 41 | console.log("Serving stencila on :%s", port); 42 | 43 | server.use(logging("dev")); 44 | console.log(stencilaDist); 45 | server.use("/stencilaDist", express.static(stencilaDist)); 46 | server.get("/app.js", (req, res) => { 47 | const appJs = path.join(__dirname, "app.js"); 48 | fs.readFile(appJs, (err, content) => { 49 | res.append("Content-Type", "application/javascript"); 50 | res.send( 51 | mustache.render(content.toString(), { 52 | archive: archive 53 | }) 54 | ); 55 | res.end(); 56 | }); 57 | }); 58 | server.use("/", express.static(__dirname)); 59 | server.listen(port); 60 | --------------------------------------------------------------------------------