├── .github └── workflows │ └── testing.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── apt.txt ├── examples ├── gnuplot-kernel.ipynb └── gnuplot-magic.ipynb ├── gnuplot_kernel ├── __init__.py ├── __main__.py ├── exceptions.py ├── images │ ├── logo-32x32.png │ ├── logo-64x64.png │ └── logo.gp ├── kernel.py ├── magics │ ├── __init__.py │ ├── gnuplot_magic.py │ └── reset_magic.py ├── replwrap.py ├── statement.py ├── tests │ ├── __init__.py │ ├── conftest.py │ └── test_kernel.py └── utils.py ├── how-to-release.rst ├── postBuild ├── pyproject.toml ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg └── setup.py /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | # Unittests 7 | unittests: 8 | runs-on: ubuntu-latest 9 | 10 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 11 | # by the push to the branch. 12 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository 13 | 14 | strategy: 15 | matrix: 16 | python-version: ["3.8", "3.10"] 17 | 18 | steps: 19 | - name: Checkout Code 20 | uses: actions/checkout@v3 21 | 22 | - name: Setup Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install Packages 28 | shell: bash -l {0} 29 | run: | 30 | sudo apt-get install gnuplot 31 | pip install -e ".[test]" 32 | pip install coveralls 33 | 34 | - name: Environment Information 35 | shell: bash -l {0} 36 | run: | 37 | gnuplot --version 38 | pip list 39 | 40 | - name: Run Tests 41 | shell: bash -l {0} 42 | run: | 43 | coverage erase 44 | make test 45 | 46 | - name: Upload coverage to Codecov 47 | uses: codecov/codecov-action@v1 48 | with: 49 | fail_ci_if_error: true 50 | name: "py${{ matrix.python-version }}" 51 | 52 | # Linting 53 | lint: 54 | runs-on: ubuntu-latest 55 | 56 | # We want to run on external PRs, but not on our own internal PRs as they'll be run 57 | # by the push to the branch. 58 | if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository 59 | 60 | strategy: 61 | matrix: 62 | python-version: ["3.10"] 63 | steps: 64 | - name: Checkout Code 65 | uses: actions/checkout@v3 66 | 67 | - name: Setup Python 68 | uses: actions/setup-python@v2 69 | with: 70 | python-version: ${{ matrix.python-version }} 71 | 72 | - name: Install Packages 73 | shell: bash -l {0} 74 | run: pip install flake8 75 | 76 | - name: Environment Information 77 | shell: bash -l {0} 78 | run: pip list 79 | 80 | - name: Run Tests 81 | shell: bash -l {0} 82 | run: make lint 83 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | dist/ 5 | build/ 6 | *.egg/ 7 | *.egg-info/ 8 | MANIFEST 9 | 10 | # Installer logs 11 | pip-log.txt 12 | 13 | # Unit test / coverage reports 14 | .coverage 15 | .tox 16 | htmlcov/ 17 | .pytest_cache 18 | coverage.xml 19 | 20 | # other 21 | .cache 22 | examples/.ipynb_checkpoints 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Hassan Kibirige 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build docs clean 2 | BROWSER := python -mwebbrowser 3 | 4 | help: 5 | @echo "clean - remove all build, test, coverage and Python artifacts" 6 | @echo "clean-build - remove build artifacts" 7 | @echo "clean-pyc - remove Python file artifacts" 8 | @echo "clean-test - remove test and coverage artifacts" 9 | @echo "lint - check style with flake8" 10 | @echo "test - run tests quickly with the default Python" 11 | @echo "coverage - check code coverage quickly with the default Python" 12 | @echo "release - package and upload a release" 13 | @echo "dist - package" 14 | @echo "install - install the package to the active Python's site-packages" 15 | @echo "develop - install the package in development mode" 16 | 17 | clean: clean-build clean-pyc clean-test 18 | 19 | clean-build: 20 | rm -fr build/ 21 | rm -fr dist/ 22 | rm -fr .eggs/ 23 | find . -name '*.egg-info' -exec rm -fr {} + 24 | find . -name '*.egg' -exec rm -f {} + 25 | 26 | clean-pyc: 27 | find . -name '*.pyc' -exec rm -f {} + 28 | find . -name '*.pyo' -exec rm -f {} + 29 | find . -name '*~' -exec rm -f {} + 30 | find . -name '__pycache__' -exec rm -fr {} + 31 | 32 | clean-test: 33 | rm -f .coverage 34 | rm -fr htmlcov/ 35 | 36 | lint: 37 | flake8 gnuplot_kernel 38 | 39 | test: clean-test 40 | pytest 41 | 42 | coverage: 43 | coverage report -m 44 | coverage html 45 | $(BROWSER) htmlcov/index.html 46 | 47 | dist: clean 48 | python setup.py sdist bdist_wheel 49 | ls -l dist 50 | 51 | release: dist 52 | twine upload dist/* 53 | 54 | release-test: dist 55 | twine upload -r pypitest dist/* 56 | 57 | install: clean 58 | python setup.py install 59 | 60 | develop: clean-pyc 61 | python setup.py develop 62 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | #################################### 2 | A Jupyter/IPython kernel for Gnuplot 3 | #################################### 4 | 5 | ================= =============== 6 | Latest Release |release|_ 7 | License |license|_ 8 | Build Status |buildstatus|_ 9 | Coverage |coverage|_ 10 | ================= =============== 11 | 12 | .. image:: https://mybinder.org/badge_logo.svg 13 | :target: https://mybinder.org/v2/gh/has2k1/gnuplot_kernel/master?filepath=examples 14 | 15 | `gnuplot_kernel` has been developed for use specifically with 16 | `Jupyter Notebook`. It can also be loaded as an `IPython` 17 | extension allowing for `gnuplot` code in the same `notebook` 18 | as `python` code. 19 | 20 | Installation 21 | ============ 22 | 23 | **Official version** 24 | 25 | .. code-block:: bash 26 | 27 | pip install gnuplot_kernel 28 | python -m gnuplot_kernel install --user 29 | 30 | The last command installs a kernel spec file for the current python installation. This 31 | is the file that allows you to choose a jupyter kernel in a notebook. 32 | 33 | **Development version** 34 | 35 | .. code-block:: bash 36 | 37 | pip install git+https://github.com/has2k1/gnuplot_kernel.git@master 38 | python -m gnuplot_kernel install --user 39 | 40 | 41 | Requires 42 | ======== 43 | 44 | - System installation of `Gnuplot`_ 45 | - `Notebook`_ (IPython/Jupyter Notebook) 46 | - `Metakernel`_ 47 | 48 | 49 | Documentation 50 | ============= 51 | 52 | 1. `Example Notebooks`_ for `gnuplot_kernel`. 53 | 2. `Metakernel magics`_, these are available when using `gnuplot_kernel`. 54 | 55 | 56 | .. _`Notebook`: https://github.com/jupyter/notebook 57 | .. _`Gnuplot`: http://www.gnuplot.info/ 58 | .. _`Example Notebooks`: https://github.com/has2k1/gnuplot_kernel/tree/master/examples 59 | .. _`Metakernel`: https://github.com/Calysto/metakernel 60 | .. _`Metakernel magics`: https://github.com/Calysto/metakernel/blob/master/metakernel/magics/README.md 61 | 62 | .. |release| image:: https://img.shields.io/pypi/v/gnuplot_kernel.svg 63 | .. _release: https://pypi.python.org/pypi/gnuplot_kernel 64 | 65 | .. |license| image:: https://img.shields.io/pypi/l/gnuplot_kernel.svg 66 | .. _license: https://pypi.python.org/pypi/gnuplot_kernel 67 | 68 | .. |buildstatus| image:: https://api.travis-ci.org/has2k1/gnuplot_kernel.svg?branch=master 69 | .. _buildstatus: https://travis-ci.org/has2k1/gnuplot_kernel 70 | 71 | .. |coverage| image:: https://coveralls.io/repos/github/has2k1/gnuplot_kernel/badge.svg?branch=master 72 | .. _coverage: https://coveralls.io/github/has2k1/gnuplot_kernel?branch=master 73 | -------------------------------------------------------------------------------- /apt.txt: -------------------------------------------------------------------------------- 1 | gnuplot 2 | -------------------------------------------------------------------------------- /examples/gnuplot-magic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Using Gnuplot in different language kernel\n", 8 | "===========================================\n", 9 | "\n", 10 | "This a Python 3 notebook, but we can switch on to use gnuplot\n", 11 | "by way of gnuplot magic. Make sure you have looked at the\n", 12 | "[gnuplot-kernel notebook](gnuplot-kernel.ipynb)." 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": {}, 18 | "source": [ 19 | "We can do the usual python stuff, but the key is the `%load_ext` magic\n", 20 | "that we use to load `gnuplot_kernel` as an extension. This allows us\n", 21 | "to use the gnuplot magic" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 1, 27 | "metadata": {}, 28 | "outputs": [], 29 | "source": [ 30 | "import numpy as np\n", 31 | "import matplotlib.pyplot as plt\n", 32 | "\n", 33 | "# inline plots for matplotlib\n", 34 | "%matplotlib inline\n", 35 | "\n", 36 | "# This loads the magics for gnuplot\n", 37 | "%load_ext gnuplot_kernel" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "Plot using matplotlib" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 2, 50 | "metadata": { 51 | "scrolled": true 52 | }, 53 | "outputs": [ 54 | { 55 | "data": { 56 | "image/png": "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\n", 57 | "text/plain": [ 58 | "
" 59 | ] 60 | }, 61 | "metadata": {}, 62 | "output_type": "display_data" 63 | } 64 | ], 65 | "source": [ 66 | "np.random.seed(123)\n", 67 | "\n", 68 | "N = 50\n", 69 | "x = np.random.rand(N)\n", 70 | "y = np.random.rand(N)\n", 71 | "colors = np.random.rand(N)\n", 72 | "area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radii\n", 73 | "\n", 74 | "plt.scatter(x, y, s=area, c=colors, alpha=0.5)\n", 75 | "plt.show()" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "metadata": {}, 81 | "source": [ 82 | "Use the `%%gnuplot` cell magic for cells that contain gnuplot code." 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": 3, 88 | "metadata": {}, 89 | "outputs": [ 90 | { 91 | "data": { 92 | "image/png": "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\n", 93 | "text/plain": [ 94 | "" 95 | ] 96 | }, 97 | "metadata": {}, 98 | "output_type": "display_data" 99 | } 100 | ], 101 | "source": [ 102 | "%%gnuplot\n", 103 | "plot sin(x)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "Use the `%gnuplot` line magic to change the inline plot options" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": 4, 116 | "metadata": {}, 117 | "outputs": [], 118 | "source": [ 119 | "%gnuplot inline pngcairo size 600,400" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 5, 125 | "metadata": { 126 | "scrolled": false 127 | }, 128 | "outputs": [ 129 | { 130 | "data": { 131 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAlgAAAGQCAIAAAD9V4nPAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd3gc1bkw8DPbi3a1u+qSZcmSVS03bLkb3ECmmBac4OCESwkE4i8PBLjB5JJsSCGB8METyMd1GoFAIAEbMAYXsA3u2MaSm3ovllZle6/fH2OWtfquzvT395ckj3fGOp5555zznvcQkUgEAQAAAEIlYvoCAAAAACZBIAQAACBoEAgBAAAIGgRCAAAAggaBEAAAgKBBIAQAACBoEAgBAAAIGgRCAAAAggaBEAAAgKBBIAQAACBoEAgBAAAIGt2BsKen580336T5pAAAAMBYCDqLbptMpg0bNhgMhj179tB2UgAAAGAc9PUI//a3v5WVlblcLtrOCAAAAEyIvkDY2dm5b9++e+65h7YzAgAAABOS0HamX/7ylwihzz//fPzDpFJpMBik44IAAABw39Qn+OgLhJMUDAZhr+CxtLxnP/KT3ozFSnuL39rsF8uJ5ELZrAcMZffqqTgdQdA6hQxGGjzr3bG8NeAKR38iUYnuOFGQMlvB4FUB6m6NU8/0t33osLX4RWKUPFOmLZR1f+a66eO89EolFafjAYIgpv4hrAuEYCwD1d4vHr60YW9+2vzLD0G3KTh0zvvpXd0Zi1WGWXJmLw9g5x0K7b69c/Xfcoq+kxx98ja+Zd3zrc6NpwplyWKmLxBg1vGJo/4f1uveztUVy+T6y+3b9qF99+2dd3xZqM6GxzVVYB0hN7hNwd23dqx8OSsaBRFCqgxJ7rVJS36b8dn3ukJ+6LrxSiSMPvte98w7tEXfSY79efFdutwqzaff60bQ4Pzis4a+eOjSmr/nZCxWRqMgQmjGLdqKhwwfb+gIusPj/HUwFRAIOSDkjey+rbPsPv2wZyKp/H69Okf61W8G6L8wQJ3jP+0LByJLfpsx8o9W/N9MnyV05vfQ4rzyxQ8vFd6RnLNKPfKPFmxN05fJP7sb3n6oQncgXLZs2ebNm2k+Kdcd2nJJnS2pfDp9rAPW/C2n9q9m05ceOq8KUKd1h71lu/26t3MJ8SjzHyIpsf7d6edeMXfuddJ/bYAKTW/bBs95F/96jHucQGv+muPsDp6G911qQCBkuzO/Gxg441n7+jQ09pSwMl1y9Z+y9/9Xd9CDc/DkF7/4BcZPA5M0dM578MGe9e/lKlK/GR8b1haqTMm1/5z22fe7He1+2i8QYL41XJeCRx7tXff6NIlyzAeyWEHc8P702r+Ym/9jw3hqQGJdWiBkKsYy1/o+XNP27a8K1TnSCQ/+9K5uZZp4xUtZNFwYoIjXHHq3smXJbzNGHQYf5sxzg607bLcdKhDLMCTOAWZE0K4bOzIWKyt/MeaQT9TAV56dVe3fay2RaWFW6zIsIQN+m6xW+2dz+Q/0k4mCCKGrX8lq2W7vOQi1ezjs6GN9BbcOT5AZy1VPpKoyJDUvDFJ9VYA6F/9i9gwEF/wsbTIHpy1QTluT1PiWleqrEhoIhOwV9IQb3rKW32+Y5PFyvXj1X3IO/qAnAsll3OS3hdo+sC/YOqlnIkIIEWjh/6TX/tUCORQc5TWHTvzMtO6NaSLJZPv0sx7UX9xmpvSqBAgCIXs1/9uWsVilyZtUd5A0fX2SLFnc8zl0Cjmp6R3btHXq2KnBCaVXKqVJop4voMU5qfkd2/TrNPqyOBYBT1uTFPRE+k64EzhdV1dXX1/fqH9kMpk6OzsT+Ex+gEDIXhe2WSoenGx3MKrke7qGf8LICSfVvWYpuyfuIkGld+vqXrNQcT2AavX/tJZ8Txff3yFQ+f36i9vibnGPx7Nx40aJZPRV+RKJZOPGjW53IvGVByAQstTQOa+rJ5B3Q1K8f7H4u8ltH9gDThge5Rhrg8/REci9Lu4WL71b377T4bOGqLgqQB1bs9/R5p+2bpSFg2O5dOnSl19+mXkbavvQ7jVfbvHm5uaamhqv1xt7ZENDQ01NjcfzzZKq3/zmNzfeeGNqamp9ff2pU6fIH/r9/s8//3xoaCglJeW222771a9+NeV/FidBIGSpC9vM5ffrR11GNj5luiRzuartQzsVVwWoU/tXS9k9+snPFUUpUsQ5q9Ut70GLc0z9G9bizbpJtng4HL7nnnvmzp375JNPViwu8c/sa3zT2tXVtWTJkqqqqocffjgvL++jjz5CCDmdziVLlmzcuPHRRx/Nysp65513EEJ2u/1Pf/rT3XffTX69fPnyQ4cOIYS2bt36yCOPJCUlIYS+//3vv/rqq1arEMeToHgdGwVc4eZ3bHeem5nYXy/5nq7+NUvxXXEOuQDmhIORhrestx6YkdhfL7tH/9XvBsrvp6T2OqBEBDW+Zb1+x/RJHv7GG2989tlnDQ0NBoPh+PHjD1+7Vfv/tr2z97Hp06cfPXpULBa/9957mzdvbmpqOnLkiNPpvHDhAkLo3XffPXz48J133vnxxx/PnDlz+vTpCKFFixY99dRT999//0svvbRt27ZTp07J5XKEUHZ2dmlp6a5du0Zd6n3S2H/ql/34/v3jqfxF+iLjxItJcIqwDAsviX4X/2z+5NaOhP960BP+a0qto8uP8ZIApVo/sG1f0ZLwXw8Fwn/PqjPXejFeEqBUz+fOf81qnPzx3/nOdx555JHot+Fw+K3yhhLJokOHDkV/OH369B07drS2tiYnJ69fv/5Pf/pTa2sr+Uf/5//8n/vuuy96ZCAQWLRokUQi+fOf/xx7lgceeOChhx5K8J/EECwhA4ZG2ejCNvOs+NNkosQKouB2bdPbUIGCM+pesyaQJhMlkhCl39PV/wNSZjij4Z/W0v+Ko8U9Ho9Wq41+SxBE6b26JcHbk5O/WXKq1WrNZvOMGTOqq6uXLFny9ttvFxcXP/jggwghk8mUmpoaPVIikRQUFASDwbS0K9bqpKam9vfT1O1jFQiErDNY4/UOBHOvjWMKfaSS7+nqX4fHIjd4+oOXvnDN3DipRfRjKbtPX/+GNRyAFYUcEPJGWt+3F2+Ko8WLi4vPnz9Pfu33+xctWqRe65gjWnXyQDX5Q7PZ3NLSUlxcfOjQoSNHjvziF784fPjwyZMn//znP3d1dSUlJblc36yx+fe//33gwIGXX375gQceiI18LpeLnC8UGgiErHP+/w3NetCQQJpMrOwV6qAnMlDtnfhQwLSGf1pn3KqVaqZ0M+qK5doZMijDzQmtH9jTFyknWTGKtGXLlv379//617/+4osv7r33XrFYXDy3QLXE89H/nPrHP/7x6aefbty4ccGCBcuXL5dIJA8//PBf//rXo0ePvvHGGzNmzMjMzCwsLGxvbyc/qqen56GHHnrllVe2bNmydOnSH/zgB9GztLe3z5yZYGoCp0EgZJeAI9zynj2uMZPREahks67hn9Ap5ID6N6xl92DIbCq7Rw8LCjmhIf7lg3l5eadPn7506dJzzz2Xl5e3e/dugiBufX7Rjdr7Dx44+NJLL61du3bfvn0ikWjZsmU7duw4ePDgM888Ew6HDx06JJVKb7zxxqNHj4ZCIYTQa6+9tnnz5o0bNyKEXn31VafTeezYMYRQOBw+evToTTfdRMU/me2mPs2IFwsviU4X/2ze/a3E02RiWZt8f8+sCwXCWD4NUKTvS/c/ZzZEcLSSzxb6i67WbQpg+CxAGVdf4C/62oArhOXT3p7T1PO5czJHLl26dOfOneMc8PHHHy9duhTLVdEJS8iAHiG7dHziKPzWlOaKopJnyrQFsq59MFbGanV/t5Tdox9nj63Jk2lF+TdroCIzyzX9y1pwq1aiwvPsLbhN27F7Uvf4s88+++KLL45zwIsvvvjb3/4Wy1VxDgRCFgkHIz2fu+KqNDG+0u/rGt6AxyJ7RUKRlvdscRfZGlvp3fpGyBZmtwTGRccxvSqpc69jMkdec801a9euPXfu3Kh/evHixauvvnrVqlW4LoxbYEE9i/Qdd2sLZco0bI0y89vJx580+awhuS6OOs6ANv1feVVZ0qTcOJImxpe1QmVt8HnNIYUBWpyNzBd93qFQ9jXYXnYzFimdnQF3X1CVOfFz42c/+9lYfzRr1qxZs2bhuirOgR4hi3R/5poef6nJccj14uyrVZ17YHSUpbr3O3PXYnsmIoTEMiJruRr2pGStlu22md9OJvA9dwkxkbNa3fUZ3ONTAoGQRTr3OhKouTy+nDVJ8Fhkre7PnNPWYW7xaWvV3fvhschS3Qdc09bibvF1Sd0QCKcGAiFb+CwhS50vc6kK78dOW6PuPgA3CRuFvBHTKU/21Th7hAgeiywW8kYGzniyVmC+x/PWJ3XudcLmzFMBgZAtuj5zZl+tFstxpA/GSKlQBBxhR0cA78eCqbt02JU6VyHTYr4HU+co/I6wo92P92PB1JEtLk3C3OKafJk0STR0AapnJA4CIVt0ferMvZaC4kYEylml7jkIXQTW6d6Pf5QMocst3n0AxsNZp+ega9oaSgqY5V6XBAulpgICIVt0f+bCPkFIylmt7oZpQvbp+sw5DWumTNS0tUnd+6HFWaf7gDNnNSUtnnttUtenEAgTB4GQFawNvnAgoi+VU/HhOWtg0oh1vEMha6MvYzHm6SJS7rVJXZ/BpBG7BBxhc60vcwk1Lb42qfeYO+gJU/HhQoBtyVowGCS3wpo/f35VVdXIA/x+/44dOzo7O4uKijZs2CCRwBLGb3Tuc05fT1XRd12RTCQlrI0+XTElgRYkoOegK/tqtViGeUqYpMmTyrSioQvelNkKKj4fJKDnc1fmYpVYQUmLSzWi1LmK3iNuSqZXBABPjzAcDldVVb300ks2m+2hhx568sknhx3g8/mWL1/+5ptv+v3+3//+96tWrQqH4eXlG1RNEH4NJo3YBvsKwmFyIXeUZboPUjUuSpp+HYyOJg5PINyxY0dzc/MXX3zx7LPPHj58+I9//GNbW1vsAUeOHGlsbPzoo4/+53/+58MPPzx69Gh0TxAQDkR6D7spyZv4Ws5qWE3ILl0UrCCMNW1tUhdME7JJzwFXzhoqX32u00C+TMLwBMKDBw+uW7dOKpUihHJycubMmbNnz57YA3JycpxO54033vjLX/7yjjvuePDBBwsKCrCcmgf6jrl1xTJFCoU1sXLXqbsPOCPQCWcHR0fAbw+nVFA4bpmzWn3pkCvkh3lCVvAOheztgfQFSupOkb5Q4ewOuHuD1J2Cx/AEQpPJlJWVFf02IyOjr68v9oDS0tIXX3zR5/MFAgG73R4MBsmdsUZFXMloNGK5SNbq+tRJUb5olDpHqtCLzbDSiB26PnXmrkvCsuPEWBQpYl2x3PSlm8JzgEnrPuDMXqkSSSlsckJMTFuTxO9aa0ajkRgByydjmyOMvSCCIESiKz55375927dvJ3dYPn78+L59+95+++2xPm3YTlG8D4Sd+6idICTlrEmCaUKW6N5P1cKJWLnrYBEFW/QcdFE6QUji/SIKo9E46n6EU4cnEGZlZfX390e/NZlMmZmZsQf84x//qKysJL9WqVSLFy8+f/48llNznXcoZGvyY6+sNlLOalhWzw4R1PO5axqV00UkKDrKHt0HqFpKH+vysnoYDo8fnkC4atWq/fv3+/1+hFBvb29NTc3q1atjD8jIyDh06FAwGEQI+f3+M2fOVFRUYDk113V95sy+Rk3pmAlp2mr1pUPuSAjuEoYNnvPKtGJNvozqE2WvVA+e9frtMDPMMNeloHcwmDKH8qUsmjypTCcePAczIHHDEwhvv/32vLy8q6+++umnn165cuX9999fXFyMELrzzjtfeOEFhNATTzxhMpmqqqqeeeaZFStWFBYWfve738Vyaq7rpnjhRJQyXZKUK+3/Cm4ShnVTVlBmGLGCyKhUXjoEo6MM697vzFmtxrj10jimQ621hOBpHLFYvG/fvieeeEKj0bzyyisvv/wy+fM1a9bMnj0bIZSdnV1XV3fvvfcqlcqf//zne/fuFYth41CEEE2jZKRpa9U9sBMF06gqMToa2ImCDXoOuqatpqnFc9aoez6HV5+4EbgmG3EhCNZdEnU8A8G3ipvuGyqj522xbafj/J+Gbt6bT8fJwGhC/sjf0+q+11ZCzw7yppOeA/f1bDo/k4ZzgbG8MaPx5j15uhI66jq5+4Jvz2q6b7CM0pxkVsESMqDWKJP6T3nSK5X0REGEUM41qr7j7pBPKO8ZLNR/yqMrltMTBRFC6QsUrp6Apx/WljHG3uoP+8P0REGEkCpTIk0S2VphE674QCBkkulLT8ZiCtfYDiNLFhvKYG0Zk0wn3JnLKM8QjiLERMYipemkh7YzgmHoHAknpS9Swj0eLwiETDKddKdX0vdYRLCakGn9pz3pC+l79UEIpS9S9Z+CQMiY7oPOnFU0JQGQMhar+uHVJ04QCJkTQf2nPBmLaH0sZq9QmU7A2yJjTCfpbvGMSqXpJLQ4Y0wnPFkr6Q2EMAYQPwiEjLE2+6UasSqT1u2o0hYq+095YMktI7xDIZ85lFxE62ZY6ZXK/pPQ4szwDoZ8lpBuJuVrRmOlL1QOnfNCKkBcIBAyxvSlm+bOAUJIlSGRqET2dphLZ4DppDt9IX25USRVpkSiFtnboMUZcHkknN4ETolKlDxTNnQeVgzHAQIhY/ppHyUjpS9U9p+GkRMG9J/0pDPR4jBWxpT+0540eqeESemLlKYvocXjAIGQMaYv3RmLac2UIaUtUA5AfRkmmE550iuZeCxWKiFfhhEDX3ko3XppLBmLVDAxHBcIhMwI+SPmi77U+ZSXHxwpfaFy4Ct4LDJg4LQnYxEDrz7plZA4yoz+0570hQzc4xmLlZA4GhcIhMwYrPHqimVSNQO//8tDozCVTi9HRwCJkDqb1twoUkalcqDaEw5Ck9PKbQoG3WFNHq2ZMiTDLIWrN+izjLnnKxgGAiEzTF+605noHCCEFKlimU5sa4HsCVqZTrozmRgJRwhJNaKkXKml1sfI2QWLrBvFSKkzQoTS5isgFWDyIBAyg6lMGVL6QrhJ6NbP0AQhKb1SaYLRUXrRXzwhVgbky8QDAiEzTCc9jGTKkNIXwDQh3fpPMZMySsqAfBnaDXzlSWMiU4aUDvky8YBAyACfNeTuC+hLGZg8IKXBCgp6RcJooNrLSAIhKX2Rqh8ei/Tq/4rRHuFi6BHGAQIhA0xfetIWKAkxYxulZFQqB854IrB1OV0sdT5VhkSuZ2wPztS5CmujP+iBJqeJszuAwigpV8rUBSRNk4okyNERYOoCuAUCIQP6TzKzgjBKlixWpkusjZA9QRPTSQaqCMUSywl9mXywBtaP0oSppfSxMhapYBuKSYJAyAD6Ky+PBPVl6MRspgwpfRFME9JngNFxUVLGYqgoNFkQCBlA/6YTI6VBvgyN2BAIIV+GTv2nmakpEysdeoSTBoGQbvY2PyFG6hzGJg9I0COkTcgXsdT5UucxUGEkVnqlCvoHtBn4yst8j7BSOXjWC4UUJgMCId1MJz0ZS5icICSlL1AO1sBNQofBGq+uRC5RMnyv6cvkbhNUG6GDo90vkhGqLAaqCMWSakSa6VLzBUgFmBgEQroxsvvSSFKNKGma1FoPNwnl+k+5GR8XRdFqIzAeTr3+08x3B0mQLzNJEAjp1s/oUvpYMDpKD6Y2nRjp8ia9gGL9X3nSFzA8Ek5Khx24JgcCIa3CgcjgWW/aVax4LKYtUPbDfkzUY7acXizYj4keAyxYO0HKWAw9wkmBQEgrS51PM10q07Li1w49Qhr47WFnT0BfJmf6QhBCKAMqjtIgggbOMJ8ySkqZLXe0BwIuKKQwAZxP5N7e3hMnTlgslrEOsFqtJ06c6OzsxHhSbhms8abOY8UdghBKu0phPu8NByBfhkL9pz1p85QiCWNVhGJp8mXhQMR1Kcj0hfCZrdUv1YiU6QxnypBEEkJXKod8mQlhC4RPP/307NmzH3300YKCgu3bt4884JVXXikoKHjqqacWLlz44IMP4jovtwye86bMZkXnACEkUYk0+VLzRbhJKNR/0s1gre2R0iuVUIuZUsxuOjFS6hzF0DmYAZkAnkB47NixP/7xj9XV1cePH3///ffvvfdem80We8DZs2e3bt166NChAwcO1NbWbt++/csvv8Ryam4ZOu9NmcOKWXRS+kIlpBFSaqDam3YVm1p8gXLgDDwWKcTsphMjpcxRDEIgnAieQPjhhx+uX78+NzcXIbRq1arc3NwPP/ww9oC3337729/+9qxZs9ra2lQq1eDg4OLFi7GcmluGznpT57LosZi2QDkA04RUGjzrTZ3Lrsci9A8oxbYeIbT4ZOAJhB0dHQUFBdFv8/Pz29vbYw+oq6sTiUSzZ89etmyZXq9/9tlnx/k04kpGoxHLRTLO0x8MByKM15SJlV4J+TIUCnkjzq6Arpix/bZGSpktHzoPj0XKRNBgtZdVPcLLQ6O8yAQwGo3ECFg+Gc+MrtfrlUq/eb7LZLJg8IoJeafTeejQoS+++GLOnDlnzpy5+uqrly5dumrVqlE/LRLhRaONMHiOXeOiCKHUOQpzrS8cjLAkm4NnzBe9uiKZSMqi323yTLnHFAw4wlINK1KXecbW6pclixQGxvbbGkmRKpaoCEdnQJPHolfwxBiNxpH9IiyxEM/NYDAYYicFLRaLwWCIPUCr1V5//fVz5sxBCF111VVr1qw5dOgQllNzyGCNl/GCk8NIVKKkHImtyc/0hfDT4DlvCptGwhFChAjpy6FTSJUh9r3sIoRS5yphdHR8eAJhZWXlyZMnya99Pt/Zs2cXLVoUe8DChQubmpqi3/b19aWkpGA5NYcMnfemzGbdTWKYrYDHIkXY2eKpc6DFqcLOFod8mQnhCYSbNm3q7Oz8yU9+sn///k2bNlVUVCxbtgwh9Pzzz3/wwQcIofvvv7+np+eHP/zhoUOHHn/88a6urk2bNmE5NYcMnWXj22IKBELKDJ3zsfCxCK8+1GFpIJwthx7h+PAEQp1Od/LkyUAg8MILLxQXF+/cuZP8eXNzc29vL0IoIyPj7NmzWq32d7/7ndvtPn78+LCxU94LByPWJr+hnC2LCKNSZiuGzsNSQkoMnfemsu/VJ3WOYugctDglhs6z8dUndS4kjk4AW/mDnJycl19+edgPt23bFv06LS3tueeew3U6zrHU+TR5UomKdRkKKTBQRg13XzASjjC+F89IKXMVg2QaIYuSePgg5I04OwPJRSxKEibpSuWOzkDQHWbh84cl4PdCk6FzbOwcIISSC2We/qDfDtUIMWNtiysMYqmacHQFmL4QvjHXenXFMrGMde8XIgmhK5aZa2EYYEwQCGnCznQyRKYRlsrNtdApxIyFq2WiYJE1Fdg5LkpKmaMYPAstPiYIhDRh9WNxtsIM04S4sTNvggT1J6kwdN5rYHGLm2EGZGwQCGnC2h4hgmoj1DCzOBBC4igVhthUUn8YWEExPgiEdPAOhoLuiCaXpZUd4LGIXTgYsTT4DbPY+licDT1C/Fg9BjBPMQRDo2ODQEiHwbOelDkK1ibpQXY1dtZGf1IuG5OESfoyub09EPLys5YhI7yDoZA3ksSmSsKxlGkSkZRwdkOG1OhYeqPyzNA5dm06MYwyTUJICHcv7NeKDZtHyRBCYhmRXCgz18HEMDaXu4NsfdlFCKXA++7YIBDSYZCVFUZipcyGKQSc2DxKRoLRUbyGznsNLH71QZAhNS4IhHQYYl/x5WEgXwYvNudGkaDF8eLAqw/ky4wNAiHlwsGIpd7H2rwJElQcxYu1q+mjoKIQXpwIhNAjHAsEQspZG/1J06RSNat/1SmzYZkRNn5byGsJafJZV2orFjwWcYogSx3bX3b1ZXJ7G2RIjY7VT2d+GDrnTZnD6jsEIWSYJbfU+8NBuEkwGDznTalQEOy+t5KmScOBiKcfMqQwsLX65XqxXMei/XhHggypcbD7ZuUF9o+SIXKH3mkSWzPs0IuB+byP5ROEJEMFjI7iwf5xUVLKHMXQWQ/TV8FGEAgpx+biarFgWT0uXHksQhohLmyuIhQrZQ7suTY6CISUY+d+vCPBxoS4DLJ7EWEUtDgubC63HSsVSm+PAQIhtbzmkN8e0rI7b4IEC8vwiCDzBc70DyCfHouh89x49UmdqxiEodHRQCCk1uX1ZCyuNxEFC8uwsLf7ZcliuZ7VeRMkQ4XcWu+LhCBDakpCvoijM5BczIFAqMqSECKoITUKCITU4sp0ESJ36DUFA07YoXdKuDJKhhCSqkWqLIm1CTKkpsRc60suZON+vKOCGlKjgkBILUutL6WCG49FQkzoy+RDF+AmmRL215SJBePhU8eVTBlSSoXcArtwjwCBkFrmWp++nANjJqSU2QrzBciemBJOrJaJgvoyUzd0ngMLhaP0ZXJYSjgSBEJqWep8+jLO3CQwTTh1Q+ehRygsHJr+QAjpy+QWCIQjQCCkkHcwFA5GVBkSpi9ksmCF9RSFfBFHR0BXzIEkYVLKbLn5IjwWp2Togs8wi1OBsBZafDgIhBQy13kN3BkXRZcLT0AgTJytyaedIRVJuZE3gRDSFshcvcGgBzKkEuQ1hwLOsGY6S/fjHUmZJkEiBKX1hoFASCFujYsihFQZsEPvlJhrOdbiIgmhLZDZIHE0UZd3YObMmw9CCBnKFDA6OgzOQNjb23vixAmLxTL+YadPn7bZbBjPy1qWOp++lEuPRYRQSgUkjibOUs+xQIgQMpTLzTBWlijzRc6khUfpy6DFh8MWCJ9++unZs2c/+uijBQUF27dvH+uwvXv3Llq06Pz587jOy2bcShkl6cvklnq4SRJk4VqPEEH2xNRY6rxwj/MAnkB47NixP/7xj9XV1cePH3///ffvvffeUft8AwMDDz30kETCmeSRKbLU+QzceyzCsEnizHU+Qzn3+gfQ4gnj3PQHgnyZ0eAJhB9++OH69etzc3MRQqtWrcrNzf3www9HHnbvvfc+9thjKpUKy0lZLuAM+8yhpOmcSSAkwWMxYZFQxNbs51DKKMkACxU5e3sAACAASURBVMumwMzJl11o8eHwBMKOjo6CgoLot/n5+e3t7cOOeeWVV0Kh0I9+9KMJP424ktFoxHKRNLPU+3QlcpbvzjqSAQJhouxtAVWmRKLiWJPriuX2FtiTORE+ayjoCquzOZMyStLkSv32kN8WYvpC4mY0GokRsHwynpvW6/VKpd/8b5DJZMHgFZmHFy9efP7551977bXJfFrkSlwNhBycLkIIqbIkIX/EO8S9m4RxnEsZJYkVhDpHYm+FxNG4Wep9+lKOpYwihBCB9CVySwP3WtxoNEZGwPLJeAKhwWCInRS0WCwGgyH6bTAY3LRp0wMPPNDb21tTUxMKhZqbm/v6+rCcmrXMHJw8IOlK5NYG6BTGzVLn5dwoGQnSCBPDxQlCkr4MKo5eAU8grKysPHnyJPm1z+c7e/bsokWLon/qcDjsdvtf/vKXW2+99dZbb3W5XFu3bn3rrbewnJq1LHU+bq2mj4JJo8Rw97EI4+GJ4W6LQyrAMHgC4aZNmzo7O3/yk5/s379/06ZNFRUVy5YtQwg9//zzH3zwgV6vb4+h1Wrffffdxx57DMupWQtuEqGx1HFvtQwJWjwxljqfvoxjScIkyJcZBk8g1Ol0J0+eDAQCL7zwQnFx8c6dO8mfNzc39/b2Djt4xYoVycnJWM7LWiF/xNkVSC7kWAIhCR6LiYggS4NfX8LNQFgOa2YSwd2XXUM53ONXwLakLycn5+WXXx72w23bto08cteuXbhOylq2Ro7VnIwFM0YJcPYEpGqCExvTj6QvlVvqfSiCuJf3wZyQN+K6FNTO4FjKKElbIHNdCoa8EbECmhwhqDVKEe5myiCEtDNknv5gwAWFmOPA3VEyhJBMK5LrxI6uANMXwiWWBp+2UCaScDKQiCSEdobU2gjvu5dBIKQEd8dMEEKECCXPlFkbuZddzSCOrp2IgvHweHGxblQsmCaMBYGQEpwOhOhyGiFkV8eBB49FyKePC+fvcZgYjgGBkBLmWg4PlCHoH8SPuymjJOgfxIvrgRDu8VgQCPGLhBEXa07GgpskXlx/LMJSwnhxOg8AwT1+JQiE+Nnb/KoMiVTN4d8t3CRx8Q6FwsGIKoPD26roIZ8+HpFQxN7K8ZfdErmtGWrMXsbhhzVrcbTKaCxdsdzeHoCbZJLMtV6OVhGKUqZJEIE8/cGJDwUI2VoDqkyJRMnh56dYQaiyJPY2SBVGCAIhFbg+ZoIQEssJdbbE3gKJo5PCg1cfhJABtqKcNK6PhJNgWX0UBEL8+HGTQPbE5EGLCw13C6zH0kNy+NcgEOJnqeP8QBmC7Il4cHFj+pFgYnjyePPqAy1OgkCIn6Xery+Fm0RA+PNYhNJ6k8ObFodiiiQIhJi5egISJVdrTsbSw4zR5AScYZ85lJTLyZqTsWBodPIsDXx42TWUKy7XmBU8CISYmTm7DeEw+jI53CSTYanz6UrlBPfvJE2u1G8P+W0hpi+E7XjzsivTimQakbMHEkchEOLGjzETRN4kWpGzG26SCZhruV1c7RsE0pfILfWQKjwBHqSFR8HADwkCIWac3oVgGLhJJoM3rz7o8rJ6SCOcAK9aHKYJEUIQCLHj001iKIdJo4lZ6nnU4vBYnARLPV/GAGAp4dcgEGJmqfPpSzhceCmWvhRukolZuF9WJuryxDAYFz/qJ5B0xTJocQSBEC+fNRRwh9XZnE8gJMEKigmFfBFnd1BbwJdXH1hBMQm8mv4olVshEEIgxMva4NcVyxEn96weBQTCCVkbfdoCqUjKkyZPLpS5eoNBT5jpC2EvnzUU9ITVWRwusB5LnS0NesI+q9BThSEQ4mRt8OlLeDJmghBSZUrCwYh3UOg3yTgsdT4erCeLIsSEtkBmbYTE0TFdHhflyZsPQgRKLpJbG4Te4hAIcbI0+PSlPBklI+lLYdJoPNYGn45Hrz4IIV2xzNoILT4mM4/GRUn6Erm1QegtDoEQJ0s93x6LMDo6Pgs5GM4j+hLoH4yHT2nhJF2JzAKBkOkL4BVro59ngdBQLjfDwrKxWRt9nN6ddSQd9A/Gxae1EyQY9UEQCDG6vGl1Eb8ei8VyK5QaGRv/Xn10xdA/GI+lzqfjy/ookq5EDrPCEAixsbcHlGliiYpXv1IYNhmHuzcolhEKA+drTsaCx+I4Qr6I61JAO4NfgbBYZm/xR0KCLiuM7an9ySeflJeXK5XKpUuXNjU1jTxg27ZtxcXFarW6oqJi+/btuM7LHvzLm0AIaQtkrkuBkFfQN8lYrI186xwghBQGsUiK3KYg0xfCRrZmvyZPxpvVMiSJUqRMFzs6BF1VGE8gbG9v37hx43PPPWexWG6++ebrrrsuGLziRvr000+3bt36z3/+0263P/vss5s3b66pqcFyavawNvAqk54kkhCaPJmtBboIo7A0+HiWKUPSFcM04eh4eY8jhHQlQp8mxBMI33rrrZUrV950000KhWLr1q2hUGj37t2xB/T39z/11FOLFy8Wi8UbNmwoLy8/deoUllOzh6XBz6dFhFH6Ehk8FkdlbeDbBCEJEkfHYmngTwHFWLCCAk8grKurq6ioiH5bVlZ24cKF2APuuuuuxx9/nPy6paWltrZ20aJFY30acSWj0YjlIqlmqefhQBki3xaFfZOMxdrIz8eirgSWEo6Ol9Mf6PI9zoFXH6PRSIyA5ZPxBEKn06lWq6PfqtVql8s16pFdXV033njj448/Pnfu3LE+LXIlrgRCHt8kAn9bHIuVv0Oj8OozKgtPxwB0HBn1MRqNkRGwfDKeQKhWq91ud/Rbt9ut0WhGHnb48OElS5b84Ac/+NWvfoXlvOzht4cDznBSDk/KbcfSc+RtkWbhQMTRFeBNue1YOhgaHYOtkVc1FKNgKSGeQFheXl5fXx/9dthIKemNN9644447/va3vz322GNYTsoqlnofn8ptx9KVyKA+/Ui2Fn/SNKlYzsMmT54pc3T6wwFIFb6CZyCIEFKk8mq1DCkpRxpwCrr0Np5AeOeddx48eHDnzp2BQOD5558Ph8Nr165FCB04cOD8+fMIoU8//fSHP/zh66+/XlFR0d3d3d3d7XQ6sZyaJawN/JwgRAgp0ySE6PJTAETxdSQcISSWEUk5UnsrdAqvwL8Cit8gkK5Y0OtH8QTCwsLC7du3/+xnPzMYDNu3b9+1a5dCoUAI/fd///frr7+OEHr22Wc9Hs/111+f+7X//d//xXJqluDZvhPDJBfDWNlwfE0SJnEle4JOfE0SJnFlmpAi2HbVqqqqqqqqGvbD06dPk18cOHAA14nYydLgK7wjmemroIq+RGZp8GWtUDF9ISxia/SlVyqZvgqqfJ04OspMv2BZebp2giTwFRS8qgfGIEs9z/sHQr5JRmXh79AogjX1o+F5iwt7lRQEQgwiYWRv8SfPhLdFAbHyemgU1tSPxO+hUX2p3CLg8voQCDFwtPuVaWJpEm9/mTBjNIzPGgp6w6pMbDMLbKMrkcOa+ljhQMTR6U/m42oZksBLb/P22U0nfo+ZIISSi2SODsin/8bl7iAPl05cps6WBD2Czqcfxt7qT8qRihW8bXKJSqRMEzs6BVp6GwIhBvweM0EIiWWEOltqb4NO4WV8rSkTK3mmoPPph+FrTZlYQi69DYEQA36nk5Fg0iiWhb/LRqMEnk8/DI8XCkcJuaIQBEIMLA0+HR83Z4kFO/TGsjbyfDAcka8+ME34NX4vFCYJeZ8ZCIQYWOr5f5PACopY1gY/74dGdVBFIQbv8wAQQrpS4a6ggEA4VQFHOGAPJ03jYbntWDA0GhUJI1uLX1fE+4EyGAP4Br9Xy5D0MEcIEmap9+mKZTxOICQJfL1tLGenX2Hg82oZkq5Ebmv2R8JMXwcL8H61DClpmjTgCPvtQmxynt/MNBDCmAlCSJ0tCXnDXjPk0wsigRAhJFWL5Hqxs0ug+fSxLs998P1lFxFIVyTQaUIIhFPF410IhtEVy21NMDoqiARCkpCzJ2Lxfn1UlGAHfiAQTpUQJg9IkC9DsjbyfxEhCerLkISwPook2HscAuFUCWHtBEkP2RMIIUG9+hRDaT2EBDP9gQScEweBcEoiYWRr5n8CIUmwb4vDCGE1PQnW1JMENP1RKtDEUQiEUyKQBEKSkCswRQXdYe9gSDOd56tlSLDrCEIoEorYWwNCedktlgkzVVgQT3DqCCSBkKQrltlbA4KtT0+yNvm1hTJCzPsMQoQQ0uTLPAOhoFt4z8UYjo6AMk0sUQniUSlVixQpYmen4EZHBdG61BFOAiFCSKIUdH16krXBpysWSosTIqSdIbU1C+6xGEs4E4QkYe65BoFwSoSTQEgScllekhBqTsYSbD59lHDWTpB0JTIBpgpDIJwSa6NfOP0DBNOEgmxxePURyNoJkjAnhiEQToml3qcXxtoJkl6Qb4uxBDdQVgwtLrQWF+KrDwTCxAU9AkogJMEKClsT//ediCXM/kEs4aydIAnz1QcCYeKsjX5tgVQgCYQkgQ+NuvuChAQpUsRMXwh9hJk6ERVwhP02/u8tE0uTJxVgqjAEwsQJ7VURIaTJlfqs4YBDWDdJlNAyZRBCihSxSILcpiDTF8IMa6MvuUhGCOkxSYgJbYHUKrCqwkJqYdwE+FhEBEqeKbM2CbRTaG0UVgIhScjj4ZYGYaWFkwTY4vQFwk8++aS8vFypVC5durSpqYm281JHaAmEJMFWI0SXH4uCbPFGgba40NZOkATY4jQFwvb29o0bNz733HMWi+Xmm2++7rrrgkHOD7YILZ2MJOSNywU4GI7I7AnBtnijsNZOkATY4jQFwrfeemvlypU33XSTQqHYunVrKBTavXs3PaemjtASCElC3prH2iiUfSdiCXlNvVWwQ6MCu8dpCoR1dXUVFRXRb8vKyi5cuEDPqSniNgkugZAkzGVGCKFwIOLo9GsLhNc/EN5A2WURZG3yJwuj3HYsAaYK0xQInU6nWq2OfqtWq10u11gHE1cyGo10XGKchPmqiKJr6oVXedve6k/KkYrlAlotQ0qeKXN0+MMBwTW5sycgTRLJdYJ72VUYWJoqbDQaiRGwfDJNgVCtVrvd7ui3brdbo9GMdXDkSmwNhEIcJUMIyZLFUrXIdUlwpbcFtdNILLGMUGdL7W3C6iIgoU4Jk9iZOGo0GiMjYPlkmgJheXl5fX199NthI6VcZG0UYgIhSYAjJ4hsceHlTZCEmSos2JddJLzEUZoC4Z133nnw4MGdO3cGAoHnn38+HA6vXbuWnlNTRJgpoyRhblwu2MFwJNRUYUG/7AoscZSmQFhYWLh9+/af/exnBoNh+/btu3btUigU9JyaIsJcYETSFQsuqQwJu38gzBYX9suusFKFJbSdqaqqqqqqirbTUSocjDg6/cnCSyAk6UvkPQfGzHXiK2ujoB+LTe/YmL4Kugn6ZReGRsGELicQKgSXQEgS4ECZ3xYKuMLqLPpeHFlFL7zB8JAv4uoNaPMFVG47ltBShSEQJsLaIMSl9FHaApnrUiDkFcpNgsiU0WI5EuibD1JnS4OesM8aYvpC6GNr9mvzZSKpQJtcaKnCEAgTYWkQbgIhQkgkITR5MluLUG4SdDmTXrgtjgiUPFNuE9KOBFZB1pWNJahUYQiEibA2CjeBkCS0reqtjcLbaeRKumJhjYcLOVOGpBPSPQ6BMBFCnkUnsXO9LXUEPhiOhNjigg+ExQJKHIVAmAhh1qSPpSsW1pp6gQ+GI+GtsBZmgfVYgkochUAYN78tFHCE1dkCTScjCWpNfSSMbM1+XZGwH4sCW2Et5NX0JEG1OATCuF3ej1eg2WSX6YU0UObsCsj1YqlG0DeLrlhubfJHwkxfBy28g6FICCnTBbpahpSUIw04hZIqLOh7OzEwi44QUqZLIhHkHRTETQKdA4SQVCOS60TObkEUW4eRcIQQIpCuSCaQVGEIhHGzNgo9U4akKxZKUpm1QegpoyTh5MtApgxJOIXWIBDGDRYYkYSTVAavPiTh5MtApgxJOPkyEAjjBv0Dkk4w620t8OqDEBJS9oSQdxqJJZwWh0AYpwiytfiThZ1ASBJOvgwMlJGEM1AGc4QkfYlQdh2BQBgfZ3dAphXJtPB7E0rhiaAn7DYFNXmCXi1DEshAWSQUsbf5k2dCICS33xJEqjA80ONjgTGTryXPlNla/eEgz0tv25r8yQUykUTYy2UQQghp86XuvkDQw/Pnor0toMqUSpTwbERSjUiWLHL18D9VGBo7PlBcLUqiFKkyJI4Ont8kkCkTRYgJ7QyZrZnnnUKoGxVLL4zxcAiE8bE1wU3yDX2p3FrP85sECqzH0pfKLbxvcRj1iaEvldsEMB4OgTA+lnqfrhRukst0xfyfS7fUQ97EN8hJI6avgloWGPWJIZAMKQiE8bHU+/QQCL8mhOwJ2IAplhCqKAh978krCWEMAEEgjEvQHfYMhDR5cJNcpiuR8f4msTYKfQOmWPpS/q+ZgcHwWPpSGe+nPxAEwrhYG/3JM2UE/M6+xvuhUbcpSIiQIlXM9IWwha6U51UUAo6wzxpOmgarZS5Lmi7zDoUCTp6nCsNDPQ4wLjqMJlfqt4f9dt7eJNYGn74MWvwbCoNYJCXcpiDTF0IVa6NPVwQvu98gREhbKLPyvfQ2NHgcLPVQYeRKBEoulNmaeNsphFefkfidKgx7y4wkhPFwCIRxgAVGI+lL+ZxUBstGR9KV8nliGJaNjiSEfBkIhHGwQv9gBF0pnxNHoUc4Er9XWFsbfPpSeNm9gq6E/6W3sQXCjo6Ohx9+eP369Vu3bvV4PCMPqKmpeeCBB2644YYtW7a0tLTgOi99IsjaBG+Lw+lLeD1QBoFwBH2p3FoPrz4CAj3CyXI4HCtWrFCpVI8++mhtbe2GDRuGHVBbW7ty5crCwsLHH39cLpcvWrSop6cHy6lp4+gKyHViaRL0oa+g4+9NEvJG3L1BTT4kEF6Bxy0eCSNbE6yWGU5XIrc18bz0tgTLp7z55psZGRl/+MMfEELXXHNNVlbWiRMnlixZEj3go48+2rBhw09/+lOE0Jo1a/bt27dnz5777rsPy9npAeOio9KXyGzN/kgY8S/Rztro0xZIodz2MNp8qdsUDHrC/CtL7ewKyA3wsjucVC2SG8TOrgCP92DB0+SnTp1aunQp+bVCoZg3b96xY8diD/jpT3/6r3/9i/za4XBcunRp+vTpWE5NGyiuNiqJSqRIETs7eThWBqNkoyLEhLZAZuNjPj20+Fh4v/konkBosVj0en30W51OZ7FYRj0yEAjcfffdlZWV69atG+vTiCsZjUYsFzlFlgZIGR2drlRu4eOkkbUBHouj0/M0cRQC4VhYMh5uNBqJEbB8coKB8PDhw+u+Vl1dLZVKg8Fv1tgGAgGFQjHybw0MDFx77bWhUOj9998f5x8QuRJLAqEVFhGOQV/CipsEOyi+PBa+Zk9YYRHhGPQlMjakChuNxsgIWD45wUCYnZ1969fS0tJmzJjR0dER/dOOjo78/Pxhf+XcuXOVlZXLly9///33lUplwlfMFEuDH94WR6Xj6bAJ9A/GoivhZ6E12GlkLDpeV1FACSfLFBYWbtmyJfrtzTffvGHDhs7OzunTpx8+fLitre26665DCNlsNqlUqlKpmpub16xZ89JLL23evBnPhdMr4Aj7bSGoQDgqfamsZbuN6avALYKsjdA/GJ2+VH72xSGmrwI/GAwfi75EbuHjq08UnjnC5cuX//jHP543b97KlStvueWWV199NS0tDSG0du3an//85wihp556ym63G43GmV/729/+huXU9LCQe3VC/uBoeDlQ5uwOyLRimRYSCEehL5FbG30Iz6AUW5BVc5Ny4GV3FEnTpH57iMdVhfEsn0AIGY3GH/3oR21tbUVFRdHEmW3btul0OoTQf//3fz/88MOxxxcWFuI6NQ1glGwc6hxp0BX2WUNyHX92aYAWH4dUI5Ili53dgaRc/oSNyxOE8LI7KgLpiuTWBl96JfdmtSYDWyBECKWlpZEdwagFCxaQXyxcuBDjiegHe3WOj9y4PGMRf24SSz2U2hoPOQzAp0AIrz7jI6sK8zUQwsjPpMBNMj7+7UgACYTj498KCiusjxoXvyuOQiCcFNiFYHw63hVittT79dDiY+NfqjBUzBgfL1MBoiAQTiwSRrYWv64I3hbHpCvhW/8AHovj0/OuioKl3gevPuPQlfCtxWNBIJyYvc2vTJdIVPC7GhPPtu4MOMN+W0jDowkw7HjWP4iEIva2QDK87I5NVyyzt/ojIX7lCn8NHu4Tgy3KJqQrlttbA+EgT24SSz2slplA0jSp38affHp7W0CVKeFfGXGMJEqRMl1ibw8wfSGUgIafGGTKTEgsJ1RZEnsbT24SaPGJEWSqME86hTAuOhn6Ut7my0AgnBhkykwGn0ZHYbXMZPBpdNTa4NPBqM9E+NTiw0AgnBj0DyaDTzcJtPhk8Cmf3tIAPcKJ8bXGLIJAOBlWuEkmgVePxXoosD4xPr36WKHFJ4Gv+8wgCIQT8llDQU9YlYWzBA8v8eaxGAlF7K3+5JkwUDYBPq2ggNUyk8Gn6Y9hIBBOwFIHo2STouPL26K9PaDKgNUyE9MVy+0tfh6kCnvNoXAgosqAl90JqLIkIV/Eaw4xfSH4wd0+AWsDvCpOiipDgsLIO8T5m8QKE4STI1YQqkyJo4PzqcJW6A5Omq5YxptU4VgQCCdgaYBSW5PFj06hpR4SCCeLHzVmITdq8vi6Qy8EwglA/2Dy+FF/EhIIJ0/Hi9LbUG578vi6Qy8EwglY6mEXgsnix44EkEA4efzIkLLAQuFJ05fKLXWcb/GRIBCOJ+SPODqgAuFk8aRHCDNGk6bnxcIyGBqdPH253FLrZfoq8INAOB5rg087QyqWQdHJSeFBPj0kEMZFx/0eYTgQcXYGtIXwsjspyTNlzp5g0MOTGrNREAjHY6716cvhVXGykgtlzq5AyM/hfHpIIIyLKkMSDkY4nSpsa/En5cLL7mSJJERyoYwHwwDDQCAcj6XWZyhXMH0VnCGSEknTpbZmDt8k5lqfoQwCYRz0ZXIzl8fKoK5svAzl3G7xUUEgHI/5otcwCx6LceB67Qlo8XgZyuWWWg63uKUOXn3io+d4i48KAuF4zLU+AwyNxkPP8a3qzRd9hlkwBhAHwyyF+SKHWxxSRuPF9RYfFQTCMYX8EUd7ILkYbpI46Mu4nT0Bs8LxMsySmy9yeKAM1kfFi+stPioIhGOyNfo0+TCLHh99OYffFn3WkN8e0uRKmb4QLuF2/yCCLLUwGB4fXiaOQiAc09BFH9wh8TKUy631vgg375HL46Lw5hMPdbYkHIh4BzmZOOroDMi0YrlOzPSFcIlIQmgLZNZGDufEjYQtEHZ0dDz88MPr16/funWrx+MZ6zCHw7Fhw4bz58/jOi91IGU0AdIkkSJNYm/j5E1ivuhNgVef+HE3cRRyoxKTwrvRUTyB0OFwrFixQqVSPfroo7W1tRs2bBjryC1btuzatctms2E5L6XMtV6YLkqAYZaco2NlZhgDSAh3R0eHLkBuVCL4lziKJxC++eabGRkZf/jDH6qqqv79739/9dVXJ06cGHnYO++809raqtFosJyUauaLPugfJCClQmG+wMm3RUgZTQx3syegR5gYQ7nCDIFwpFOnTi1dupT8WqFQzJs379ixY8OO6ejo+OlPf/rmm2+KRByYmAwHIo72QHIR3CRx43KPEB6LieByi/sMFfDqEzfutvhY8MQki8Wi1+uj3+p0OovFEntAKBS66667fvvb3+bl5U34acSVjEYjlouMi7XBp8mXiuWQOBE3jvYPfJZQ0BNRZ0PKaNwMsxRDHBwDiISRtQFW0yciuUjm7A7QnzhqNBqJEbB8coKB8PDhw+u+Vl1dLZVKg8Fg9E8DgYBCccV71q9//evc3Ny77rprMh8euRIjgRCW0idMXya3NvnDQY5VHB264DXMkkPKaAJUmRKEkGcgOOGRrGJv8ytSJVINBwao2IapxFGj0RgZAcsnJ/ifIDs7+9avpaWlzZgxo6OjI/qnHR0d+fn50W/tdvuvfvWrvr4+8niXy/XUU0/961//muKlU8pcC9NFCZIoRepsib2FY4mjkCkzFYYy7uXLQItPBddL6w2T4HYzhYWFW7ZsiX578803b9iwobOzc/r06YcPH25ra7vuuusQQjabTSqVyuXyv/71r9GD9+3bt3LlyrKysileOqXMF72FdyQzfRVcRaYRcqtgB2TKTAU5aZSzSs30hcTBfMGbAhOEifq69DZPHpJ49l1bvnz5j3/843nz5s2aNevixYuvvvpqWloaQmjt2rWrVq36wx/+8F//9V/Rgx955JHrr79+/vz5WE5NEQsMjU5BSoV86IK34HYt0xcSB/NF74xbuJHPzEJcnBg2X/RNX5/E9FVwlWGWovFfVqavAhtsG5AajcYf/ehHbW1tRUVF0cSZbdu26XS6YUfu2rVr9uzZuM5LhXAgYm8L6KDKaKIMsxStH9iZvor4mC/6UqBHmCjDLEXzu5xrce+8x1KYvgqu4lniKM6duNPS0siOYNSCBQtGHrZixQqMJ6WCtdGvyYOU0cQZZslP/4ZL/QPvYCgciKiyYGP6BHGuRxgJRWzNsO9E4pKLZM6uQMgbESv48JyEjKlRwHqyKdKVyO1tXNqqHlp8ipTpEkJEuE2cSRy1NftVWRKpGh6ACRJJCG2B1NrIk04h/D8YhbnWp4cqo1MglhOa6VJbE2cSRyFTZur05VwaK4PialPH3dJ6I0EgHAVkykwdt8bKhqBHOGXcanHzRW9KBbT4lHydOMoHEAhHAQNlU2eo4NLbIvQIpy6FU/0DaPGpgx4hn0HKKBaGWXIOld2y1MLa6qniXI8QWnyK9OVy3pTehkA4HKSMYsGht0VPfzASiqgyIGV0SjjU4uFAxN4aXT8KBgAAH+9JREFUgJTRKdIVyZydgZCPMzlx44BAOBxsQ4gFeZPQX5Y3AbAFARaKVLFISrh7OZA4Ci+7WIikhLZAam3gxtvP+CAQDgeTB1iIpIS2kIGyvAmAUTJcDLPkQ1wYHR264DVApgwOHBoGGB8EwuEgZRSXFI5MGpkvQovjwZXHIrzs4sKbxFEIhMOZa73wWMSCK0WY4LGIC1fyZWAMABfe5MtAILwCzKJjZKhQmLmQOGqug8ciHvDqIzRcGQOYEATCK1ib/EnTYRYdD048FsmqYMp0SBnF4PJjkd1ZhCFvxNkZ0BXJmL4QPricOOpld5NPAgTCKwzWeFLnwKsiHsmFMndfMOBideIodA4wUhjEEiXhuhRg+kLGY2nwaQtlIim87GIgkhLJRTJOjIePDwLhFYbOelPmwmMRD0JMJBfJrPWs7hTCdBFe7B8rM1+A4mo4pc5TDJ6FQMgvg+e80CPEKKVCMXSB3Y9F2IYQK3JPZqavYjxmSAvHKnUuBELeGazxps6HxyI2+nK5hd3Z1YM1nhR49cHHMJvtj0XzBS/UT8AIAiHfePqD4UAkaZqU6QvhD8MsVvcII6GI+aIPAiFGafMVg9WsfiwOXYC6sjilzlMOnfWyPENqQhAIvzFY44VnIl4pFaxeWGZt9KsyJTIt3AXYGGYpbC1+1qYRBt1hd18wuRBSRrFRpIglapGjgwM1pMYBj4BvDJ71ps6DQIiTJl/ms4R8lhDTFzK6gWpP2nwl01fBK2I5oSuSsXaacPCcV18qJ8SQMooTD/JlIBB+Y7DGmzYPHos4ESKUOlc5wNaxssFqmBLGL3WeYrCGrS1+xpu2AFocs9S57G3xSYJA+I3BGg+sncAudb5i4IyH6asY3UC1F3qE2KXOVw5Us7bFYQwAPx7ky0AgvCzkjdjbA/oymEXHjM3ZE4NnPTAYjl0ai3uEAzAGQAEYGuWPoQteXbFMLIPJA8zSrmJp/8DZFRCJCVUmFFfDLHW+cui8N8K+gkLhQMRa74OFwtglz5R7BoJ+G0tTASYDAuFlg2e9qTAuSgF9udzZGQg4WfdcHKj2pl0Fo2T4ybQiZZrE1sS6ZTPmiz5NnlSigoceZoQIGWYphs6zrsUnD9vrcDAY3LFjR2tr6/z586uqqkY9Zs+ePTU1NTk5ORs3blQo2BV1hiAQUkMkIfRl8qHz3sylKqav5QqD1R4YJaNI2nzFQLWXbbu4DFR74NWHImnzFIM1nqwV7LrHJw/Py1E4HK6qqnrppZdsNttDDz305JNPjjzmO9/5zuOPP+73+19//fUlS5Z4vewaUx6s8aZCyig10q5SDpxhV3Ojyy0OgZASqfOULJwmhCRh6qRwPF8GT49wx44dzc3Nzc3NUql0y5YtRUVFDz744IwZM6IHfPzxx8eOHbt48aJWqw2FQt/5zncaGhrmzp2L5ewYRNDQBW/KbLhJKJE6XzFwmnXThAPVnmXPZzJ9FfyUNl9x9o9DTF/FcANnPAW3aZm+Cn5Knauo+7uF6atIHJ4e4cGDB9etWyeVShFCOTk5c+bM2bNnT+wBn3zyycaNGy0Wy2uvvXbw4MF3332XRVEQIXu7X5okUqSKmb4Qfkqbz7qlhOQyf20BVBihROo81qUKR8Jo8ByMAVAlZY7CUusLB1laUWhCeAKhyWTKysqKfpuRkdHX1xd7QFtb27lz56qqqvbt23fffffdcMMNodCYKUbElYxGI5aLHMdgDUwQUihljtxS7wv5WXSTDFR7U+YoCEiboIY6R0qIkKuHRRsT2pp8yjSJXA8vu5SQqkXqbIm1kdpCa0ajkRgByycnODRaX1//3nvvkV9v3rw5HA7HXhBBECLRFc+YQCBAxkK1Wm2328vKyv7zn/9s2rRp1A+PROh+YkJxNUpJlCLtDKn5oi+NNTM0gzWwsJpaKXMVgzVedQ5bStgPVHvZ89+Pl8jq25RucWU0Gkf2i7DEwgRfia1Wa83XHA5HVlZWf39/9E9NJlNm5hWzL2lpaUuWLFGr1QghrVa7ePHiurq6qVw3XkNnvalz4bFIobSrlINsWk04WA2vPtRi23j4YLUnFV59qMTpfJkEA+GSJUve+9rs2bNXrVq1f/9+v9+PEOrt7a2pqVm9enXs8atXrz527JjT6UQI+f3+mpqasrKyqV89LgM1sDE9tch8eqav4hsDNV54LFIqdZ5isIZFrz4DZ6BHSK3Uuexq8bjgmSS5/fbb8/Lyrr766qeffnrlypX3339/cXExQujOO+984YUXEEJ33313UVHRqlWrnnnmmVWrVuXm5t5xxx1YTj11flvIOwg7s1Ardb6SPRVHQ96IvdUP25RTin2vPrCIkFqcLrSGZ/mEWCzet2/fBx980NLS8sorr6xfv578+Zo1a/Lz8xFCMpls7969u3fvvnDhwiOPPHLrrbeSKaZsMHgW8iYol3aVcui8NxKKsGEHnKHzXl2xTCxn/kp4LLlI7hkI+iwhNuSnODqhnB7lkqZJw8GI2xRUZXDv94ztisVi8be+9a1hP3zggQeiXxMEccMNN9xwww24zogLFFejgUwrUqVLrE1+fSnz/bCBag8UT6AaIUIpFYqhc97sa9RMXwsarPakLYAWp1zqHMXQWa/quiSmLyRu0A+C4mo0SWXNNhSDNTBdRIfU+YoBdtSXGaj2pl0FLU651HlKlrR4vCAQogEorkaLNNZsUzcAKaO0SJvHllThgTOwWoYOqXMVQ9ycJhR6IAwHI5Y6n2EW8+N1vMeSiqORMDJfhDEAOqTOZ8vGhFBllB4pcxWDZ1nx6hMvoQdCa4M/KVcqTRL674EGaVcpBs54ENPlZayNPlW6RJbMfAYH76XMVlgb/SEvw03uHQwFXGFtPqSFU85QLre3BYIe1u25NiGhB4DBGg90DuihTJdIlISjk+GyW9A5oI1YTiTPlJlrGe4UDpzxpM5TIMgRpp5ISuiKZOaL3NuYUOiBEKaL6JR2FfOrCQehihCNUucxPzo6UA0ThPRh1YrhyRN6IOw75mbbhrE8lsqCslsDZzyQQEgbNiyrHzgDKaP0yVyi7DsOgZBTQr7I0HlveiW8LdIkbb6C8TRCWDZKp9R5SsbLbsGe23TKXKbqO+5m+iriJuhAOFDt0RXLpWpB/xLoxHj/wNHuF0kQe7ZE4L20qxSDZ73hAGP5MgFH2NUT0JdCpgxNDLMU7r6gd2jMXfbYSdAxoO+YO3MZjIvSR5MvC/nC7r4gUxdw6Yg7aznzhU6EQ5Ys1s6QMThNOFDtSZmjYENhP4EgRChjkdJ0gmOdQkEHQtMJT+YSGDOhVepcJpfVm064M5dCi9Mqi9GxMsiGo1/mUu6Njgo6EPYec2cthx4hrTKXKvuOMXaT9B51Z0KPkF6ZS1UMtnjfUciGo1vmUlUvcy2eGOEGQkdHIBKMaGCZLb2yVqh7jzBzkwQcYVuzH/oHNMtcrrrEUIsjhHqPurJXQiCkVcYSZf9pTzjIdO2MeAg3EPYdd2dCd5B2mUtV/ac9IT8DN0nfCXfaVUqxDKaLaJVcKIuEIs4uBgop2Fr8iCDgZZdmcp1YkysdOs+lZfXCDoQwZkI7mVakK5IxsuQWcqOYkrlU1XuUgU5h72HoDjIjc5nKxKlpQgEHQlhKz5CsFerewwzcJPDqwxSmsid6j7qzVsCUMAM4ly8j0EAY9IQtdT6oN8GIrBUM9A8iYWT60gMpo4zIXMZMvsylw+6sFfDqwwDO5csINBD2n/IYKuQSpUD/+czKvlrde9gVobdCvfmCV5khUaZJaD0rQAghlL5QaanzBZy0Nrl3MOQxBVMqYIc1BuhL5X5riMEVw/ESaCToOw7TRYxRZUpkOrGlnta59N5j7ixocYaI5UTKHEX/aVonhi8ddmUuU8FSemYQKGOxqo87y+oFGwg9MF3EoOyVqt4jLjrPCBOEzMpaTvd4eO8RWCXMpMylSg7lywg0EJpOwGORSVnL6V5N2HcMVsswif7sid4jrixIGWVO5jIVh7ahEGIgtDX7RTIiaRpUXmZM1koVnYHQ0x/0DoX0pTBdxJjM5aq+Y27aJoaD7rC51pdRCYGQMRmLVANnmFkxnAAhBsK+4zBdxDB9iTzgCju7aVpk3XvUnblURQjxPztbqDIkcr3Y2kDTxHDfCU/qXIVYAROEjJFqRMkzZUNnGd6NcpKE+GzoO+7OgHFRZhEoaxl9k0YwQcgGWcvoS6nvPeyCFYSM49AiCmyBMBgM/uc///nd7363d+/eUQ/w+/3vvPPOc8899/777weDTKbVwlJ6NshcTt/oaB9UV2cBOlcT9h6FFYTMy1zKmfoyeAJhOByuqqp66aWXbDbbQw899OSTTw47wOfzLV++/M033/T7/b///e9XrVoVDtO7juxrAVfY1gKVl5mXvVJNT+JoyBcZPOtNr4Sl9AyjLRCGgxHTl7A+inkc2q0ez/riHTt2NDc3Nzc3S6XSLVu2FBUVPfjggzNmzIgecOTIkcbGxpMnTxIE8YMf/CAzM7O9vb2goADL2eNiOuFOnaeAysuMS7tKYWvx+20hWbKY0hMNfOXRlcilSUKcBWCVlNkKV2/QOxhSpFLb4oM13qTpMoWB2rOACSXPlIV8EWd3gP2ZiXieDgcPHly3bp1UKkUI5eTkzJkzZ8+ePbEH5OTkOJ3OG2+88Ze//OUdd9zx4IMPMhIFEUJ9xz2QKcMGIimRvlBJQ4I1LKVnCXLvchoWWfcecUOtbZbIWKw0neDAIgo8gdBkMmVlZUW/zcjI6Ovriz2gtLT0xRdf9Pl8gUDAbrcHg8FQKDTWpxFXMhqNWC6SBJky7JG1go7RUciUYQ96xsp6j7hggpAl8K4fNRqNxAhYPjnBodH6+vr33nuP/Hrz5s3hcDj2ggiCEImuCLH79u3bvn37F198gRB66qmnSktL33777c2bN4/64ZEIVUtPwoFI33H32tdyKPp8EJfsFarTvxmg+ix9x90r/m/WxMcB6mUtU53+LeUt3nvUvfwFaHFWyFqpPrTlEq5PMxqNI/tFWGJhgoHQarXW1NSQX99yyy1ZWVn9/f3RPzWZTJmZmbHH/+Mf/6isrCS/VqlUixcvPn/+fGKnnopLh1y6ErkyHSovs0LmMtVAtSfki4jlVE3Z2lr8BIE0eWyfohCIjCWqga88IX+Eukl6a5NfJCGgxVkic4nS2R1wdAY001ndIgkOjS5ZsuS9r82ePXvVqlX79+/3+/0Iod7e3pqamtWrV8cen5GRcejQIXLVhN/vP3PmTEVFxdSvPl7tHzvyb0ii/7xgVNIkka5YPvAVhVMIl75wZV8N68nYgtyWuf8khS3eewRanEUIMZG3XtO528H0hUwAzxzh7bffnpeXd/XVVz/99NMrV668//77i4uLEUJ33nnnCy+8gBB64oknTCZTVVXVM888s2LFisLCwu9+97tYTh2Xjl2O/A1a+s8LxpK9QnWJytWEbR/a8zdoqPt8EK/8Ddr2j+zUfX7vEVhByC55N2raPxZGIBSLxfv27XviiSc0Gs0rr7zy8ssvkz9fs2bN7NmzEULZ2dl1dXX33nuvUqn8+c9/vnfvXrGY7uRma6Mv4I6kzoUVhCySfY2654CTog8PesI9X7jzrodAyCIzbta07aTwsdh9wJV9DfQIWWR6VdKlL1xBDzMLxycJ22yZWCz+1re+NeyHDzzwQPRrlUp111134TpdAto/cuTfpEGwgJBNplclHbi3x2cJyfX4X4y6PnWlL1RS8ckgYWlXKQOusLXBpyvBXwN94IxHJEGGcqiuziJyvTh1vrLngCvvRva+krJxlbH5IiWVeTs+ceTdwN6WECaJSpR9jbrjE0q6CO0fwbgo+xAo/yaqOoWtHzgKb4e5D9bJv4mq0dHOPXjGk9gYCJv+bcP+mX57uP+0J3cdjJmwTsFt2tb38U8aRcKofZdjxs0QCFlnxi3atp2UTBO27rAV3J5MxSeDqci/UdO+y4EoWBZX95oFy+ewMRA2/wd/IOzc48haqZao2PjvFbgZt2i6PnMFXJinEEwn3Mp0iXaGDO/HgqnLWa02X/C5TZgr71ub/D5rOGMRFJVlHX2ZXCwnBs9h3pIp6A537uVvjzDkDZsvYP6VtX/syGfxCLWQyXXijEpl16eYU2badjpm3AKjZGwklhG51yZhHw9v3W4ruBWSAFjqcqcQq47dzgxMxfTZGAgLv5Xc/B7OkZNIGHXtdeZdDysIWYqK0dG2nXYYF2Wt/Js17binCVvftxfcBq8+LJV3o6YD9zRhy7u2wo14WpyVgXCjthnrNKHphFuZIdHkwygZS824Vdv+kSPkxzaHYGv2+22h9AUwSsZS+Tdpug+6gm5s4+GunoCt1Q8LJ1gr+xq1udbn6cc2Hh7yRjr3OQtu5W8gzFysCrrD5lpsuaPtHzvyb4LOAXupsyW6EvmlL7AV4G79wD7jZi2MkrGWXCdOu0rRfQBfi79vz79JK5JAk7OUWEbkrlPjSvJECHXsdqRdpcRVL5ONgRARqOB2bcu72DqFHTBByHp4R0fbdzryYVyU3WbcjDN3FMZF2Q9viZnmd22Fd2BrcVYGQoRmbsQ2TejsCji7AxmLYZSM1Qpv17a+b4/gGCrzDoWGznunrYYpYVYruFXTvtOBq8VhcRT75d+o6drnxDIDEvJGOvc4Mb76sDQQZi5VBewhSx2G0dH2XY68GzSEGMZMWC15pkyZJjHh2LW1fZdj2jq1WAEtzmqafJkiTWI6iaHF23bac69LgsVRLKdMl+iKZb04agt37HGkzlOoMrBVRmPrfx0CFdymbXkPw+ho+8cONpf2AVG4Rkfbdtpn3AyjZBxQcAue3FEYF+UKXLmjLe/aZ27EWTmBrYEQocI7kpvfnepjMegJ9x52Ta+CUTIOKLhN27J9qi0e8kV6Drig0DYn5OOYJgw4w5e+cEH1RE7Iv0nT9iGGe7xjtwNXviiJvYEwc5nKOxS0NkxpdLTlPXvmUpVcB2WXOSB1ngIRaGhq5Se6DzhT5igUqdDiHJBRqfRZQrYW/1Q+pHOPM3MZ3OPckDZfGYkg05dT2pCyc68zZY5ClYVzf3X2BkJCNOWV9RFU/fzgvJ+k4rsoQK2C27QtO6b0wti6HdbRcweB8m+a6jAAjItyCYHmPZpy5rmBqXxGy3u2mfjyRUnsDYQIocI7prSIomOPgxCj3GthXJQzpjhN6LoUbP3AXvJ9HcZLApSa9UPDuT8OhbwJZhKG/JGO3VBLj0vK7tX3HnFbGxMc6gv5Iu27HIXfwlxandWBMGuF2t0ftDYlOHJS/dzg/CdSYVU1h2QuVQVd4YRX1lc/N1D6/9u786Cm8jsA4C+JCQmI3CSIsGA5u4JyLrKeLDZMp5aKFHSLGRFn0dXp6iq7qFjRXZmtoDJWppaKM7oix1CKVwd0aookIiKXiKIbIiJHEkggKEeOl/QP3IyzIHK9vCTv+/krefDIN/zmm29+7/2OrTYMh7m8ZgIw5RBAdwikP70wwz0Enl0cYIYy5nD0IMDaPHPykp22jSelMzv91e03dkvm+LooYuCFkERGPP5o9TRvJkkifjAy2K7yiIM9WYwJiYyEHHG8nyaewbnDIvWzHwcC9sOVcCMTctix/q+9M5hehiq0D7+XhKQ7YhEVwI7/bru2EvnMth95mtfvET/3n+oGXQgRBAn8xv7Jednrl6rpnthwonfZ13aw5JLR8frcSiFDZ7C7SkNWn/cWa3MWdA6MjGMIw+bXZs8uDkz3xJZ/yOyW0llh5lhEBbBDt6d4fm7d/LdpdwpF1cOSuhHfxLm/92HohdDCmeq3y+7+oel1EQaFyu67w76JNhhFBbBDopBC0h3vHxJPaxvPUSnaerE/IAW6g0Yp5Ijjw+OSaXUK1SOahsy+T44xsYsKYGfZ13Ytuf2q19NZWEiLVH3Vs/wHJhYrJxh6IUQQJPBb++67Q6J701iPoP5E35IvbanzjeDdgfE8Yq0QjVY4nflGDVl9nvFWFs5U7KIC2GGFmVt7mj3Pn0an8NEZKSvc3H4ZHbuoAHYWuNMWfWbx5Lxs6qc8yx8gkRCvzZgMhTOCUjHPnBya7sjfL5piF2FEom4rkfvtssU4LoAZEhJ6jFlzWDLFhShHZeiTXFlAigPGYQEMhR51rDveq1FPKclVbzRN2dJQuDtozAJTHRpPSad4GQAd1dakicOzWBgNfjSCQoggiM9WG1SpneIMs0dnpJ6brWHooFFz+50lzZI8xW0pm7Klv4q1svwIuoNGjBVubrGI+lPhlFq8IavPlT3fxtcM66gAduyX0m18zKaY4w0n+xxDGAtXYrWuunEUQhIZ+TSTde8bEar4wNcH1ZCm5Z/9S/fY6Scw05aeno7jq3/yneODI+IPdhGUcrTlnCww1cTvDuLbFvoRctjh4TGJFv1AiysG0Md/lwUfxrM7SITm0IOAFPuGzL4PXuobkaibsvvCMljYRULSaudsW/A5QSK9N6Sb6186r7WYfKWYmr9I5M8Vvyl0wSY6YpmkLfSjLOKFd4K177bJBj3VHpO8bldFXHDWW1S4wL0t9KN0hdBvt53npsnGx1enihUD6JpzC/UW1XgEaQ49KAoQLNtn750w2Z2//+3oplqQPz05cSGck7bQd4+wq6vr8uXLMzs3/ASr/oe+USk64U+1GoS/T9RWIl9+AsMvDkCfwr5n1n432Qyz7sqhR2ekgQfg7qCJCD7seP+gWPbkvcuODIvVT87LgtOgxU1ExHnn6m9FzTnvHTUja1EI/z2IdYvrtRCKxeINGzbMuBDa+Jp5xC3g7xepR345iEI9oqmI6+itH9nIX2zpCveKTAQr3NwxiH71sxd9Tb9ciVuLamuPSSo2v1qX72LtScMlPDDnXNnzgw87lK15MeFaM513hq5GvPj4C9v5iyDHTYRDECOGt7g5R8rfJxo/OE7Rj1b9uTvooIOZDbaLquuvEObl5fn6+g4NzXD1rDGh6UxFP/rj4ucPj/eOyt52DUd61WUR7fMY5PUVblj/v4CesYtdvbdYX2e3V33Vo5S/bfGhbvXVde3dlUPxdR6wx5aJ8U202VDp3pQtvf2nTt08s6Eu1a1Nr7jbu8IymGEZMHfQpCxwp23kL+6tH6mI69B1ct68UvH3iS57Prf8iOb3JeZTAPRXCDs6Om7dupWYmDibP0K3p/y2zPUPd9wHhcp8z+e8vT1d3KF/LRe6RFpEXlpEocE6MqaGREY+/sJ2c4snOqrN9/2p9eLAy/+8Lg4WLIqY//vb7nO+5CAwBDa+ZrE1i6mW5OIggbhmpP5EX+EygZWX2eYWD1hf2ySZ2VDWV7jNY5DLItq77w79d2tnUYAAISHxjR4RF5zJVOw/2LX6lZmZyWazJ/kFzN8wAAAAEzL7woTVF+rW1taSkpKxxwkJCW5ublN/S9N6IS2qJVGgI0gUWg2ieqOhLTCOaT9g9hQDKGy6Syi4fKRjVQgHBgYaGxvHHkdHR2P0KgiCQBUkFBIZgSpIKFAFiQaXj3SsCmFYWJiuRwgAAAAYLPhyDQAAgND0PeguPDycxYIJ7wAAAAwFLBQEAACA0ODSKAAAAEKDQggAAIDQoBACAAAgNCiEAAAACA0KIQAAAEIzrDWL+Xy+VqtdsWLF2FO1Wl1aWioUCgMCAthsNr6xAYCvysrKmpoaJyen+Ph4Gg12nsKNXC4/ffq07qmPj8+mTZtwjIewurq6uFxuQkKC7siMc8SAeoQPHjyIjo7m8XhjTzUaDZvNzs7OlsvlO3fuTE1NxTc8Arpx44a9vf32n+Xk5OAdEXGlp6dzOJz+/v7c3NzIyEiVSoV3RMRVVVV19uzZzp9JpVK8IyKi8bvbziZHDKJHiKLonj178vPzLS0tdQdLS0sFAoFAIKBSqbt37/b09ExOTnZ3d8cxTqLh8XgbN26Mjo4eHh5evXq1gwNsC46Pnp6ejIyM5uZmb29vFEWDgoIKCgo4HA7ecREU5AXu8vLyUlJSnJycbG3fblU4yxwxiB6hUqmkUCiPHz/28/PTHeRyuZGRkVQqFUEQZ2dnf3//8vJy/GIkIj6fX1RUdOnSpZycHB8fn+rqarwjIigej+fi4uLt7Y0gCIVCiYqKunnzJt5BERfkBe7G7247yxwxiELIYDCys7MXLlz47kGxWOzk5KR7ymQyRSKR3kMjNDabff369cLCQi6Xu23btuTkZLwjIijIBYMCeYG7o0ePBgcHv3tkljmCz6XRioqK2trasceHDh0ikSbYd0Oj0bx7nEQikckGUbZNWG5urkQiQRDE1dWVw+GkpaXpfsThcLKysvALjdAgFwzK+LyQy+VWVlY4hgRmmSP4FMLOzk7dboVarXbCQujk5DT2oTxGLBbDat1Ya21t7ejoQBBEoVAMDg4WFRUlJCQwGAwEQVAUnbCZgB5ALhiOCfNi7A4OwNEscwSfQpiUlJSUlDT576xZs+bAgQNKpZJGo/X09DQ2Nq5du1Y/4RHWqVOndI+VSuXevXutrKzi4uIQBCkuLtZNawF6tnLlSqFQKBAIPDw8UBQtLy/ftWsX3kERFJ1OH58X5ubmeMdFdLPMEYMYNTqhmJiYc+fOrVq1at26dQUFBdu3b/fy8sI7KAKh0WhZWVk7duyoq6sTiUTXrl27c+cO3kERFIvFOnjwYEREBIfD4fF4FAply5YteAdFUJAXhmmWOWJY2zBduXLF1dVV1/NAUbSsrKytrc3f3z8qKgrf2IipqamJy+VSqdSYmJh370UD/ePxeDU1NUwmMzY2lk6n4x0OoUFeGIJ79+4JhcJ3J9TPOEcMqxACAAAAegZjzwAAABAaFEIAAACEBoUQAAAAoUEhBAAAQGhQCAEAABAaFEIAAACEBoUQAAAAoUEhBAAAQGhQCAEAABAaFEIAAACE9n+qA7jpk09WBAAAAABJRU5ErkJggg==\n", 132 | "text/plain": [ 133 | "" 134 | ] 135 | }, 136 | "metadata": {}, 137 | "output_type": "display_data" 138 | } 139 | ], 140 | "source": [ 141 | "%%gnuplot\n", 142 | "plot cos(x)" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": { 148 | "collapsed": true 149 | }, 150 | "source": [ 151 | "That is it" 152 | ] 153 | } 154 | ], 155 | "metadata": { 156 | "kernelspec": { 157 | "display_name": "Python 3", 158 | "language": "python", 159 | "name": "python3" 160 | }, 161 | "language_info": { 162 | "codemirror_mode": { 163 | "name": "ipython", 164 | "version": 3 165 | }, 166 | "file_extension": ".py", 167 | "mimetype": "text/x-python", 168 | "name": "python", 169 | "nbconvert_exporter": "python", 170 | "pygments_lexer": "ipython3", 171 | "version": "3.6.10" 172 | } 173 | }, 174 | "nbformat": 4, 175 | "nbformat_minor": 1 176 | } 177 | -------------------------------------------------------------------------------- /gnuplot_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Gnuplot Kernel Package 3 | """ 4 | from importlib.metadata import PackageNotFoundError 5 | 6 | from .kernel import GnuplotKernel 7 | from .magics import register_ipython_magics 8 | from .utils import get_version 9 | 10 | __all__ = ['GnuplotKernel'] 11 | 12 | 13 | try: 14 | __version__ = get_version('gnuplot_kernel') 15 | except PackageNotFoundError: 16 | # package is not installed 17 | pass 18 | 19 | 20 | def load_ipython_extension(ipython): 21 | """ 22 | Load the extension in IPython 23 | """ 24 | register_ipython_magics() 25 | -------------------------------------------------------------------------------- /gnuplot_kernel/__main__.py: -------------------------------------------------------------------------------- 1 | from .kernel import GnuplotKernel 2 | 3 | 4 | if __name__ == '__main__': 5 | GnuplotKernel.run_as_main() 6 | -------------------------------------------------------------------------------- /gnuplot_kernel/exceptions.py: -------------------------------------------------------------------------------- 1 | class GnuplotError(Exception): 2 | 3 | def __init__(self, message): 4 | self.args = (message,) 5 | self.message = message 6 | -------------------------------------------------------------------------------- /gnuplot_kernel/images/logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/has2k1/gnuplot_kernel/facb6f3205e7d0a334aafdd29eb985caa7c1209a/gnuplot_kernel/images/logo-32x32.png -------------------------------------------------------------------------------- /gnuplot_kernel/images/logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/has2k1/gnuplot_kernel/facb6f3205e7d0a334aafdd29eb985caa7c1209a/gnuplot_kernel/images/logo-64x64.png -------------------------------------------------------------------------------- /gnuplot_kernel/images/logo.gp: -------------------------------------------------------------------------------- 1 | # set terminal pngcairo enhanced color size 1000, 1077 crop 2 | set terminal pngcairo enhanced transparent size 147, 171 crop 3 | set output 'logo-64x64.png' 4 | set parametric 5 | set urange [-pi:pi] 6 | set vrange [-pi:pi] 7 | set isosamples 50,20 8 | 9 | unset key 10 | unset xtics 11 | unset ytics 12 | unset ztics 13 | unset colorbox 14 | unset border 15 | 16 | set pm3d depthorder 17 | 18 | splot cos(u)+.5*cos(u)*cos(v),sin(u)+.5*sin(u)*cos(v),.5*sin(v) with pm3d, \ 19 | 1+cos(u)+.5*cos(u)*cos(v),.5*sin(v),sin(u)+.5*sin(u)*cos(v) with pm3d 20 | -------------------------------------------------------------------------------- /gnuplot_kernel/kernel.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from itertools import chain 3 | from pathlib import Path 4 | import uuid 5 | 6 | from IPython.display import Image, SVG 7 | from metakernel import MetaKernel, ProcessMetaKernel, pexpect 8 | from metakernel.process_metakernel import TextOutput 9 | 10 | from .statement import STMT 11 | from .exceptions import GnuplotError 12 | from .replwrap import GnuplotREPLWrapper, PROMPT_RE, PROMPT_REMOVE_RE 13 | from .utils import get_version 14 | 15 | 16 | IMG_COUNTER = '__gpk_img_index' 17 | IMG_COUNTER_FMT = '%03d' 18 | 19 | 20 | class GnuplotKernel(ProcessMetaKernel): 21 | """ 22 | GnuplotKernel 23 | """ 24 | implementation = 'Gnuplot Kernel' 25 | implementation_version = get_version('gnuplot_kernel') 26 | language = 'gnuplot' 27 | language_version = '5.0' 28 | banner = 'Gnuplot Kernel' 29 | language_info = { 30 | 'mimetype': 'text/x-gnuplot', 31 | 'name': 'gnuplot', 32 | 'file_extension': '.gp', 33 | 'codemirror_mode': 'Octave', 34 | 'help_links': MetaKernel.help_links, 35 | } 36 | kernel_json = { 37 | 'argv': [sys.executable, 38 | '-m', 'gnuplot_kernel', 39 | '-f', '{connection_file}'], 40 | 'display_name': 'gnuplot', 41 | 'language': 'gnuplot', 42 | 'name': 'gnuplot', 43 | } 44 | 45 | inline_plotting = True 46 | reset_code = '' 47 | _first = True 48 | _image_files = [] 49 | _error = False 50 | 51 | def bad_prompt_warning(self): 52 | """ 53 | Print warning if the prompt is not 'gnuplot>' 54 | """ 55 | if not self.wrapper.prompt.startswith('gnuplot>'): 56 | msg = ("Warning: The prompt is currently set " 57 | "to '{}'".format(self.wrapper.prompt)) 58 | print(msg) 59 | 60 | def do_execute_direct(self, code): 61 | # We wrap the real function so that gnuplot_kernel can 62 | # give a message when an exception occurs. Without 63 | # this, an exception happens silently 64 | try: 65 | return self._do_execute_direct(code) 66 | except Exception as err: 67 | print(f"Error: {err}") 68 | raise err 69 | 70 | def _do_execute_direct(self, code): 71 | """ 72 | Execute gnuplot code 73 | """ 74 | if self._first: 75 | self._first = False 76 | self.handle_plot_settings() 77 | 78 | if self.inline_plotting: 79 | code = self.add_inline_image_statements(code) 80 | 81 | success = True 82 | 83 | try: 84 | result = super().do_execute_direct(code, silent=True) 85 | except GnuplotError as e: 86 | result = TextOutput(e.message) 87 | success = False 88 | 89 | if self.reset_code: 90 | super().do_execute_direct(self.reset_code, silent=True) 91 | 92 | if self.inline_plotting: 93 | if success: 94 | self.display_images() 95 | self.delete_image_files() 96 | 97 | self.bad_prompt_warning() 98 | 99 | # No empty strings 100 | return result if (result and result.output) else None 101 | 102 | def add_inline_image_statements(self, code): 103 | """ 104 | Add 'set output ...' before every plotting statement 105 | 106 | This is what powers inline plotting 107 | """ 108 | # "set output sprintf('foobar.%d.png', counter);" 109 | # "counter=counter+1" 110 | def set_output_inline(lines): 111 | tpl = self.get_image_filename() 112 | if tpl: 113 | cmd = ( 114 | f"set output sprintf('{tpl}', {IMG_COUNTER});" 115 | f"{IMG_COUNTER}={IMG_COUNTER}+1" 116 | ) 117 | lines.append(cmd) 118 | 119 | # We automatically create an output file for the following 120 | # cases if the user has not created one. 121 | # - before every plot statement that is not in a 122 | # multiplot block 123 | # - before every multiplot block 124 | 125 | lines = [] 126 | sm = StateMachine() 127 | is_joined_stmt = False 128 | for line in code.splitlines(): 129 | stmt = STMT(line) 130 | sm.transition(stmt) 131 | add_inline_plot = ( 132 | sm.prev_cur in ( 133 | ('none', 'plot'), 134 | ('none', 'multiplot'), 135 | ('plot', 'plot') 136 | ) 137 | and not is_joined_stmt 138 | ) 139 | if add_inline_plot: 140 | set_output_inline(lines) 141 | 142 | lines.append(stmt) 143 | is_joined_stmt = stmt.strip().endswith('\\') 144 | 145 | # Make gnuplot flush the output 146 | if not lines[-1].endswith('\\'): 147 | lines.append('unset output') 148 | code = '\n'.join(lines) 149 | return code 150 | 151 | def get_image_filename(self): 152 | """ 153 | Create file to which gnuplot will write the plot 154 | 155 | Returns the filename. 156 | """ 157 | # we could use tempfile.NamedTemporaryFile but we do not 158 | # want to create the file, gnuplot will create it. 159 | # Later on when we check if the file exists we know 160 | # whodunnit. 161 | fmt = self.plot_settings['format'] 162 | filename = Path( 163 | f'/tmp/gnuplot-inline-{uuid.uuid1()}' 164 | f'.{IMG_COUNTER_FMT}' 165 | f'.{fmt}' 166 | ) 167 | self._image_files.append(filename) 168 | return filename 169 | 170 | def iter_image_files(self): 171 | """ 172 | Iterate over the image files 173 | """ 174 | it = chain(*[ 175 | sorted(f.parent.glob(f.name.replace(IMG_COUNTER_FMT, '*'))) 176 | for f in self._image_files 177 | ]) 178 | return it 179 | 180 | def display_images(self): 181 | """ 182 | Display images if gnuplot wrote to them 183 | """ 184 | settings = self.plot_settings 185 | if self.inline_plotting: 186 | if settings['format'] == 'svg': 187 | _Image = SVG 188 | else: 189 | _Image = Image 190 | 191 | for filename in self.iter_image_files(): 192 | try: 193 | size = filename.stat().st_size 194 | except FileNotFoundError: 195 | size = 0 196 | 197 | if not size: 198 | msg = ( 199 | "Failed to read and display image file from gnuplot." 200 | "Possibly:\n" 201 | "1. You have plotted to a non interactive terminal.\n" 202 | "2. You have an invalid expression." 203 | ) 204 | print(msg) 205 | continue 206 | 207 | im = _Image(str(filename)) 208 | self.Display(im) 209 | 210 | def delete_image_files(self): 211 | """ 212 | Delete the image files 213 | """ 214 | # After display_images(), the real images are 215 | # no longer required. 216 | for filename in self.iter_image_files(): 217 | try: 218 | filename.unlink() 219 | except FileNotFoundError: 220 | pass 221 | 222 | self._image_files = [] 223 | 224 | def makeWrapper(self): 225 | """ 226 | Start gnuplot and return wrapper around the REPL 227 | """ 228 | if pexpect.which('gnuplot'): 229 | program = 'gnuplot' 230 | elif pexpect.which('gnuplot.exe'): 231 | program = 'gnuplot.exe' 232 | else: 233 | raise Exception("gnuplot not found.") 234 | 235 | # We don't want help commands getting stuck, 236 | # use a non interactive PAGER 237 | if pexpect.which('env') and pexpect.which('cat'): 238 | command = 'env PAGER=cat {}'.format(program) 239 | else: 240 | command = program 241 | 242 | d = dict( 243 | cmd_or_spawn=command, 244 | prompt_regex=PROMPT_RE, 245 | prompt_change_cmd=None 246 | ) 247 | wrapper = GnuplotREPLWrapper(**d) 248 | # No sleeping before sending commands to gnuplot 249 | wrapper.child.delaybeforesend = 0 250 | return wrapper 251 | 252 | def do_shutdown(self, restart): 253 | """ 254 | Exit the gnuplot process and any other underlying stuff 255 | """ 256 | self.wrapper.exit() 257 | super().do_shutdown(restart) 258 | 259 | def get_kernel_help_on(self, info, level=0, none_on_fail=False): 260 | obj = info.get('help_obj', '') 261 | if not obj or len(obj.split()) > 1: 262 | if none_on_fail: 263 | return None 264 | else: 265 | return '' 266 | res = self.do_execute_direct('help %s' % obj) 267 | text = PROMPT_REMOVE_RE.sub('', res.output) 268 | self.bad_prompt_warning() 269 | return text 270 | 271 | def reset_image_counter(self): 272 | # Incremented after every plot image, and used in the 273 | # plot image filename. Makes plotting in loops do_for 274 | # loops work 275 | cmd = f'{IMG_COUNTER}=0' 276 | self.do_execute_direct(cmd) 277 | 278 | def handle_plot_settings(self): 279 | """ 280 | Handle the current plot settings 281 | 282 | This is used by the gnuplot line magic. The plot magic 283 | is innadequate. 284 | """ 285 | settings = self.plot_settings 286 | if ('termspec' not in settings or 287 | not settings['termspec']): 288 | settings['termspec'] = ('pngcairo size 385, 256' 289 | ' font "Arial,10"') 290 | if ('format' not in settings or 291 | not settings['format']): 292 | settings['format'] = 'png' 293 | 294 | self.inline_plotting = settings['backend'] == 'inline' 295 | 296 | cmd = 'set terminal {}'.format(settings['termspec']) 297 | self.do_execute_direct(cmd) 298 | self.reset_image_counter() 299 | 300 | 301 | class StateMachine: 302 | """ 303 | Track context given gnuplot statements 304 | 305 | This is used to help us tell when to inject commands (i.e. set output) 306 | that for inline plotting in the notebook. 307 | """ 308 | states = ['none', 'plot', 'output', 'multiplot', 'output_multiplot'] 309 | previous = 'none' 310 | _current = 'none' 311 | 312 | @property 313 | def prev_cur(self): 314 | return (self.previous, self.current) 315 | 316 | @property 317 | def current(self): 318 | return self._current 319 | 320 | @current.setter 321 | def current(self, value): 322 | self.previous = self._current 323 | self._current = value 324 | 325 | def transition(self, stmt): 326 | lookup = { 327 | s: getattr(self, f'transition_from_{s}') 328 | for s in self.states 329 | } 330 | _transition = lookup[self.current] 331 | self.previous = self._current 332 | return _transition(stmt) 333 | 334 | def transition_from_plot(self, stmt): 335 | if self.current == 'output': 336 | self.current = 'none' 337 | elif self.current == 'plot': 338 | if stmt.is_plot(): 339 | self.current = 'plot' 340 | elif stmt.is_set_output(): 341 | self.current = 'output' 342 | else: 343 | self.current = 'none' 344 | 345 | def transition_from_none(self, stmt): 346 | if stmt.is_plot(): 347 | self.current = 'plot' 348 | elif stmt.is_set_output(): 349 | self.current = 'output' 350 | elif stmt.is_set_multiplot(): 351 | self.current = 'multiplot' 352 | 353 | def transition_from_output(self, stmt): 354 | if stmt.is_plot(): 355 | self.current = 'plot' 356 | elif stmt.is_set_multiplot(): 357 | self.current = 'output_multiplot' 358 | elif stmt.is_unset_output(): 359 | self.current = 'none' 360 | 361 | def transition_from_multiplot(self, stmt): 362 | if stmt.is_unset_multiplot(): 363 | self.current = 'none' 364 | 365 | def transition_from_output_multiplot(self, stmt): 366 | if stmt.is_unset_multiplot(): 367 | self.previous = self.current 368 | self.current = 'output' 369 | -------------------------------------------------------------------------------- /gnuplot_kernel/magics/__init__.py: -------------------------------------------------------------------------------- 1 | from .gnuplot_magic import GnuplotMagic, register_ipython_magics 2 | 3 | __all__ = ['GnuplotMagic', 'register_ipython_magics'] 4 | -------------------------------------------------------------------------------- /gnuplot_kernel/magics/gnuplot_magic.py: -------------------------------------------------------------------------------- 1 | from IPython.core.magic import (register_line_magic, 2 | register_cell_magic) 3 | from metakernel import Magic 4 | 5 | 6 | class GnuplotMagic(Magic): 7 | def __init__(self, kernel): 8 | """ 9 | GnuplotMagic 10 | 11 | Parameters 12 | ---------- 13 | kernel : GnuplotKernel 14 | Kernel used to execute the magic code 15 | """ 16 | super(GnuplotMagic, self).__init__(kernel) 17 | 18 | def eval(self, code): 19 | """ 20 | Evaluate code useing the gnuplot kernel 21 | """ 22 | return self.kernel.do_execute_direct(code) 23 | 24 | def print(self, text): 25 | """ 26 | Print text if it is not empty 27 | """ 28 | if text and text.output.strip(): 29 | self.kernel.Display(text) 30 | 31 | def line_gnuplot(self, *args): 32 | """ 33 | %gnuplot CODE - evaluate code as gnuplot 34 | 35 | This line magic will evaluate the CODE to setup or 36 | unset inline plots. This magic should be used instead 37 | of the plot magic 38 | 39 | Examples: 40 | %gnuplot inline pngcairo enhanced transparent size 560,420 41 | %gnuplot inline svg enhanced size 560,420 fixed 42 | %gnuplot inline jpeg enhanced nointerlace 43 | %gnuplot qt 44 | 45 | """ 46 | backend, terminal, termspec = _parse_args(args) 47 | terminal = terminal or 'pngcairo' 48 | inline_terminals = {'pngcairo': 'png', 49 | 'png': 'png', 50 | 'jpeg': 'jpg', 51 | 'svg': 'svg'} 52 | format = inline_terminals.get(terminal, 'png') 53 | 54 | if backend == 'inline': 55 | if terminal not in inline_terminals: 56 | msg = ("For inline plots, the terminal must be " 57 | "one of pngcairo, jpeg, svg or png") 58 | raise ValueError(msg) 59 | 60 | self.kernel.plot_settings['backend'] = backend 61 | self.kernel.plot_settings['termspec'] = termspec 62 | self.kernel.plot_settings['format'] = format 63 | self.kernel.handle_plot_settings() 64 | 65 | def cell_gnuplot(self): 66 | """ 67 | %%gnuplot - Run gnuplot commands 68 | 69 | Example: 70 | %%gnuplot 71 | unset border 72 | unset xtics 73 | g(x) = cos(2*x)/sin(x) 74 | 75 | Note: this is a persistent connection to a gnuplot shell. 76 | The working directory is synchronized to that of the notebook 77 | before and after each call. 78 | """ 79 | result = self.eval(self.code) 80 | self.print(result) 81 | 82 | 83 | def register_magics(kernel): 84 | """ 85 | Make the gnuplot magic available for the GnuplotKernel 86 | """ 87 | kernel.register_magics(GnuplotMagic) 88 | 89 | 90 | def register_ipython_magics(): 91 | """ 92 | Register magics for the running kernel 93 | 94 | The magics themselve use a special kernel that 95 | understands gnuplot. 96 | """ 97 | from ..kernel import GnuplotKernel 98 | 99 | # Kernel to run the both the line magic (%gnuplot) 100 | # and cell magic (%%gnuplot) statements 101 | # See: GnuplotKernel for a full notebook kernel 102 | kernel = GnuplotKernel() 103 | magic = GnuplotMagic(kernel) 104 | 105 | # This kernel that is used by the magics is 106 | # not the main kernel and it may not have access 107 | # to some functionality. This connects it to the 108 | # main kernel. 109 | from IPython import get_ipython 110 | ip = get_ipython() 111 | kernel.makeSubkernel(ip.parent) 112 | 113 | # Make magics callable: 114 | kernel.line_magics['gnuplot'] = magic 115 | kernel.cell_magics['gnuplot'] = magic 116 | 117 | @register_line_magic 118 | def gnuplot(line): 119 | magic.line_gnuplot(line) 120 | 121 | del gnuplot 122 | 123 | @register_cell_magic 124 | def gnuplot(line, cell): 125 | magic.code = cell 126 | magic.cell_gnuplot() 127 | 128 | 129 | def _parse_args(args): 130 | """ 131 | Process the gnuplot line magic arguments 132 | """ 133 | if len(args) > 1: 134 | raise TypeError() 135 | 136 | sargs = args[0].split() 137 | backend = sargs[0] 138 | if backend == 'inline': 139 | try: 140 | termspec = ' '.join(sargs[1:]) 141 | terminal = sargs[1] 142 | except IndexError: 143 | termspec = None 144 | terminal = None 145 | else: 146 | termspec = args[0] 147 | terminal = sargs[0] 148 | 149 | return backend, terminal, termspec 150 | -------------------------------------------------------------------------------- /gnuplot_kernel/magics/reset_magic.py: -------------------------------------------------------------------------------- 1 | from metakernel import Magic 2 | 3 | 4 | class ResetMagic(Magic): 5 | 6 | def line_reset(self, *line): 7 | """ 8 | %reset - Clear any reset 9 | 10 | Example: 11 | %reset 12 | """ 13 | self.kernel.reset_code = '' 14 | 15 | def cell_reset(self, line): 16 | """ 17 | %%reset - Change the gnuplot terminal 18 | 19 | This cell magic is used to specify statements that will 20 | silently run after every cell execution. 21 | 22 | Example: 23 | %%reset 24 | set key 25 | """ 26 | self.kernel.reset_code = self.code 27 | 28 | 29 | def register_magics(kernel): 30 | """ 31 | Make the reset magic available for the GnuplotKernel 32 | """ 33 | kernel.register_magics(ResetMagic) 34 | -------------------------------------------------------------------------------- /gnuplot_kernel/replwrap.py: -------------------------------------------------------------------------------- 1 | import re 2 | import textwrap 3 | import signal 4 | 5 | from metakernel import REPLWrapper 6 | from metakernel.pexpect import TIMEOUT 7 | 8 | from .exceptions import GnuplotError 9 | 10 | 11 | CRLF = '\r\n' 12 | NO_BLOCK = '' 13 | 14 | ERROR_RE = [ 15 | re.compile( 16 | r'^\s*' 17 | r'\^' # Indicates error on above line 18 | r'\s*' 19 | r'\n' 20 | ) 21 | ] 22 | 23 | PROMPT_RE = re.compile( 24 | # most likely "gnuplot> " 25 | r'\w*>\s*$' 26 | ) 27 | 28 | PROMPT_REMOVE_RE = re.compile( 29 | r'\w*>\s*' 30 | ) 31 | 32 | # Data block e.g. 33 | # $DATA << EOD 34 | # # x y 35 | # 1 1 36 | # 2 2 37 | # 3 3 38 | # EOD 39 | START_DATABLOCK_RE = re.compile( 40 | # $DATA << EOD 41 | r'^\$\w+\s+<<\s*(?P\w+)$' 42 | ) 43 | END_DATABLOCK_RE = re.compile( 44 | # EOD 45 | r'^(?P\w+)$' 46 | ) 47 | 48 | 49 | class GnuplotREPLWrapper(REPLWrapper): 50 | # The prompt after the commands run 51 | prompt = '' 52 | _blocks = { 53 | 'data': { 54 | 'start_re': START_DATABLOCK_RE, 55 | 'end_re': END_DATABLOCK_RE 56 | } 57 | } 58 | _current_block = NO_BLOCK 59 | 60 | def exit(self): 61 | """ 62 | Exit the gnuplot process 63 | """ 64 | try: 65 | self._force_prompt(timeout=.01) 66 | except GnuplotError: 67 | return self.child.kill(signal.SIGKILL) 68 | 69 | self.sendline('exit') 70 | 71 | def is_error_output(self, text): 72 | """ 73 | Return True if text is recognised as error text 74 | """ 75 | for pattern in ERROR_RE: 76 | if pattern.match(text): 77 | return True 78 | return False 79 | 80 | def validate_input(self, code): 81 | """ 82 | Deal with problematic input 83 | 84 | Raises GnuplotError if it cannot deal with it. 85 | """ 86 | if code.endswith('\\'): 87 | raise GnuplotError("Do not execute code that " 88 | "endswith backslash.") 89 | 90 | # Do not get stuck in the gnuplot process 91 | code = code.replace('\\\n', ' ') 92 | return code 93 | 94 | def send(self, cmd): 95 | self.child.send(cmd + '\r') 96 | 97 | def _force_prompt(self, timeout=30, n=4): 98 | """ 99 | Force prompt 100 | """ 101 | quick_timeout = .05 102 | 103 | if timeout < quick_timeout: 104 | quick_timeout = timeout 105 | 106 | def quick_prompt(): 107 | try: 108 | self._expect_prompt(timeout=quick_timeout) 109 | return True 110 | except TIMEOUT: 111 | return False 112 | 113 | def patient_prompt(): 114 | try: 115 | self._expect_prompt(timeout=timeout) 116 | return True 117 | except TIMEOUT: 118 | return False 119 | 120 | # Eagerly try to get a prompt quickly, 121 | # If that fails wait a while 122 | for i in range(n): 123 | if quick_prompt(): 124 | break 125 | 126 | # Probably stuck in help output 127 | if self.child.before: 128 | self.send(self.child.linesep) 129 | else: 130 | # Probably long computation going on 131 | if not patient_prompt(): 132 | msg = ("gnuplot prompt failed to return in " 133 | "in {} seconds").format(timeout) 134 | raise GnuplotError(msg) 135 | 136 | def _end_of_block(self, stmt, end_string): 137 | """ 138 | Detect the end of block statements 139 | 140 | Parameters 141 | ---------- 142 | stmt : str 143 | Statement to be executed by gnuplot repl 144 | 145 | Returns 146 | ------- 147 | end_string : str 148 | Terminal string for the current block. 149 | """ 150 | pattern_re = self._blocks[self._current_block]['end_re'] 151 | if m := pattern_re.match(stmt): 152 | if m.group('end') == end_string: 153 | return True 154 | return False 155 | 156 | def _start_of_block(self, stmt): 157 | """ 158 | Detect the start of block statements 159 | 160 | Parameters 161 | ---------- 162 | stmt : str 163 | Statement to be executed by gnuplot repl 164 | Returns 165 | ------- 166 | block_type : str 167 | Name of the block that has been detected. 168 | Returns an empty string if none has been detected. 169 | end_string : str 170 | Terminal string for the block that has been detected. 171 | Returns an empty string if none has been detected. 172 | """ 173 | # These are used to detect the end of the block 174 | block_type = NO_BLOCK 175 | end_string = '' 176 | for _type, regexps in self._blocks.items(): 177 | if m := regexps['start_re'].match(stmt): 178 | block_type = _type 179 | end_string = m.group('end') 180 | break 181 | return block_type, end_string 182 | 183 | def _splitlines(self, code): 184 | """ 185 | Split the code into lines that will be run 186 | """ 187 | # Statements in a block are not followed by a prompt, this 188 | # confuses the repl processing. We detect a block and concatenate 189 | # it into single line so that after executing the line we can 190 | # get a prompt. 191 | lines = [] 192 | block_lines = [] 193 | end_string = '' 194 | stmts = code.splitlines() 195 | for stmt in stmts: 196 | if self._current_block: 197 | block_lines.append(stmt) 198 | if self._end_of_block(stmt, end_string): 199 | self._current_block = NO_BLOCK 200 | block_lines.append('') 201 | block = '\n'.join(block_lines) 202 | lines.append(block) 203 | block_lines = [] 204 | end_string = '' 205 | else: 206 | block_name, end_string = self._start_of_block(stmt) 207 | if block_name: 208 | self._current_block = block_name 209 | block_lines.append(stmt) 210 | else: 211 | lines.append(stmt) 212 | 213 | if self._current_block: 214 | msg = 'Error: {} block not terminated correctly.'.format( 215 | self._current_block) 216 | self._current_block = NO_BLOCK 217 | raise GnuplotError(msg) 218 | 219 | return lines 220 | 221 | def run_command(self, code, timeout=-1, stream_handler=None, 222 | stdin_handler=None): 223 | """ 224 | Run code 225 | 226 | This overrides the baseclass method to allow for 227 | input validation and error handling. 228 | """ 229 | code = self.validate_input(code) 230 | 231 | # Split up multiline commands and feed them in bit-by-bit 232 | stmts = self._splitlines(code) 233 | output_lines = [] 234 | for line in stmts: 235 | self.send(line) 236 | self._force_prompt() 237 | 238 | # Removing any crlfs makes subsequent 239 | # processing cleaner 240 | retval = self.child.before.replace(CRLF, '\n') 241 | self.prompt = self.child.after 242 | if self.is_error_output(retval): 243 | msg = '{}\n{}'.format( 244 | line, textwrap.dedent(retval)) 245 | raise GnuplotError(msg) 246 | 247 | # Sometimes block stmts like datablocks make the 248 | # the prompt leak into the return value 249 | retval = PROMPT_REMOVE_RE.sub('', retval).strip(' ') 250 | 251 | # Some gnuplot installations return the input statements 252 | # We do not count those as output 253 | if retval.strip() != line.strip(): 254 | output_lines.append(retval) 255 | 256 | output = ''.join(output_lines) 257 | return output 258 | -------------------------------------------------------------------------------- /gnuplot_kernel/statement.py: -------------------------------------------------------------------------------- 1 | """ 2 | Recognising gnuplot statements 3 | """ 4 | import re 5 | 6 | # name of the command i.e first token 7 | CMD_RE = re.compile( 8 | r'^\s*' 9 | r'(?P' 10 | r'\w+' # The command 11 | r')' 12 | r'\s?' 13 | ) 14 | 15 | # plot statements 16 | PLOT_RE = re.compile( 17 | r'^\s*' 18 | r'(?P' 19 | r'plot|plo|pl|p|' 20 | r'splot|splo|spl|sp|' 21 | r'replot|replo|repl|rep' 22 | r')' 23 | r'\s?' 24 | ) 25 | 26 | # "set multiplot" and abbreviated variants 27 | SET_MULTIPLE_RE = re.compile( 28 | r'\s*' 29 | r'set' 30 | r'\s+' 31 | r'multip(?:lot|lo|l)?\b' 32 | r'\b' 33 | ) 34 | 35 | # "unset multiplot" and abbreviated variants 36 | UNSET_MULTIPLE_RE = re.compile( 37 | r'\s*' 38 | r'(?:unset|unse|uns)' 39 | r'\s+' 40 | r'multip(?:lot|lo|l)?\b' 41 | r'\b' 42 | ) 43 | 44 | 45 | # "set output" and abbreviated variants 46 | SET_OUTPUT_RE = re.compile( 47 | r'\s*' 48 | r'set' 49 | r'\s+' 50 | r'(?:output|outpu|outp|out|ou|o)' 51 | r'(?:\s+|$)' 52 | ) 53 | 54 | # "unset output" and abbreviated variants 55 | UNSET_OUTPUT_RE = re.compile( 56 | r'\s*' 57 | r'(?:unset|unse|uns)' 58 | r'\s+' 59 | r'(?:output|outpu|outp|out|ou|o)' 60 | r'(?:\s+|$)' 61 | ) 62 | 63 | 64 | class STMT(str): 65 | """ 66 | A gnuplot statement 67 | """ 68 | 69 | def is_set_output(self): 70 | """ 71 | Return True if stmt is a 'set output' statement 72 | """ 73 | return bool(SET_OUTPUT_RE.match(self)) 74 | 75 | def is_unset_output(self): 76 | """ 77 | Return True if stmt is an 'unset output' statement 78 | """ 79 | return bool(UNSET_OUTPUT_RE.match(self)) 80 | 81 | def is_set_multiplot(self): 82 | """ 83 | Return True if stmt is a "set multiplot" statement 84 | """ 85 | return bool(SET_MULTIPLE_RE.match(self)) 86 | 87 | def is_unset_multiplot(self): 88 | """ 89 | Return True if stmt is a "unset multiplot" statement 90 | """ 91 | return bool(UNSET_MULTIPLE_RE.match(self)) 92 | 93 | def is_plot(self): 94 | """ 95 | Return True if stmt is a plot statement 96 | """ 97 | return bool(PLOT_RE.match(self)) 98 | -------------------------------------------------------------------------------- /gnuplot_kernel/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/has2k1/gnuplot_kernel/facb6f3205e7d0a334aafdd29eb985caa7c1209a/gnuplot_kernel/tests/__init__.py -------------------------------------------------------------------------------- /gnuplot_kernel/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def remove_files(*filenames): 5 | """ 6 | Remove the files created during the test 7 | """ 8 | for filename in filenames: 9 | try: 10 | os.remove(filename) 11 | except FileNotFoundError: 12 | pass 13 | -------------------------------------------------------------------------------- /gnuplot_kernel/tests/test_kernel.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | from pathlib import Path 3 | 4 | from metakernel.tests.utils import (get_kernel, get_log_text, 5 | clear_log_text) 6 | from gnuplot_kernel import GnuplotKernel 7 | from gnuplot_kernel.magics import GnuplotMagic 8 | 9 | from .conftest import remove_files 10 | 11 | # Note: Empty lines after indented triple quoted may 12 | # lead to empty statements which could obscure the 13 | # final output. 14 | 15 | 16 | # All kernels are registered to ensure a 17 | # thorough cleanup 18 | _get_kernel = get_kernel 19 | KERNELS = weakref.WeakSet() 20 | 21 | 22 | def get_kernel(klass=None): 23 | """ 24 | Create & add to registry of live kernels 25 | """ 26 | if klass: 27 | kernel = _get_kernel(klass) 28 | else: 29 | kernel = _get_kernel() 30 | KERNELS.add(kernel) 31 | return kernel 32 | 33 | 34 | def teardown(): 35 | """ 36 | Shutdown all live kernels 37 | """ 38 | while True: 39 | try: 40 | kernel = KERNELS.pop() 41 | except KeyError: 42 | break 43 | 44 | kernel.do_shutdown(restart=False) 45 | 46 | 47 | # Normal workflow tests # 48 | 49 | def test_inline_magic(): 50 | kernel = get_kernel(GnuplotKernel) 51 | 52 | # gnuplot line magic changes the plot settings 53 | kernel.call_magic('%gnuplot pngcairo size 560, 420') 54 | assert kernel.plot_settings['backend'] == 'pngcairo' 55 | assert kernel.plot_settings['format'] == 'png' 56 | assert kernel.plot_settings['termspec'] == 'pngcairo size 560, 420' 57 | 58 | 59 | def test_print(): 60 | kernel = get_kernel(GnuplotKernel) 61 | code = "print cos(0)" 62 | kernel.do_execute(code) 63 | text = get_log_text(kernel) 64 | assert '1.0' in text 65 | 66 | 67 | def test_file_plots(): 68 | kernel = get_kernel(GnuplotKernel) 69 | kernel.call_magic('%gnuplot pngcairo size 560, 420') 70 | 71 | # With a non-inline terminal plot gets created 72 | code = """ 73 | set output 'sine.png' 74 | plot sin(x) 75 | """ 76 | kernel.do_execute(code) 77 | assert Path('sine.png').exists() 78 | clear_log_text(kernel) 79 | 80 | # Multiple line statement 81 | code = """ 82 | set output 'sine-cosine.png' 83 | plot sin(x),\ 84 | cos(x) 85 | """ 86 | kernel.do_execute(code) 87 | assert Path('sine-cosine.png').exists() 88 | 89 | # Multiple line statement 90 | code = """ 91 | set output 'tan.png' 92 | plot tan(x) 93 | set output 'tan2.png' 94 | replot 95 | """ 96 | kernel.do_execute(code) 97 | assert Path('tan.png').exists() 98 | assert Path('tan2.png').exists() 99 | 100 | remove_files('sine.png', 'sine-cosine.png') 101 | remove_files('tan.png', 'tan2.png') 102 | 103 | 104 | def test_inline_plots(): 105 | kernel = get_kernel(GnuplotKernel) 106 | kernel.call_magic('%gnuplot inline') 107 | 108 | # inline plot creates data 109 | code = """ 110 | plot sin(x) 111 | """ 112 | kernel.do_execute(code) 113 | text = get_log_text(kernel) 114 | assert 'Display Data' in text 115 | clear_log_text(kernel) 116 | 117 | # multiple plot statements data 118 | code = """ 119 | plot sin(x) 120 | plot cos(x) 121 | """ 122 | kernel.do_execute(code) 123 | text = get_log_text(kernel) 124 | assert text.count('Display Data') == 2 125 | clear_log_text(kernel) 126 | 127 | # svg 128 | kernel.call_magic('%gnuplot inline svg') 129 | code = """ 130 | plot tan(x) 131 | """ 132 | kernel.do_execute(code) 133 | text = get_log_text(kernel) 134 | assert 'Display Data' in text 135 | clear_log_text(kernel) 136 | 137 | 138 | def test_plot_abbreviations(): 139 | kernel = get_kernel(GnuplotKernel) 140 | 141 | # Short names for the plot statements can be used 142 | # to create inline plots 143 | code = """ 144 | plot sin(x) 145 | p cos(x) 146 | rep 147 | unset key 148 | sp x**2+y**2, x**2-y**2 149 | """ 150 | kernel.do_execute(code) 151 | text = get_log_text(kernel) 152 | assert text.count('Display Data') == 4 153 | 154 | 155 | def test_multiplot(): 156 | kernel = get_kernel(GnuplotKernel) 157 | 158 | # multiplot 159 | code = """ 160 | set multiplot layout 2,1 161 | plot sin(x) 162 | plot cos(x) 163 | unset multiplot 164 | """ 165 | kernel.do_execute(code) 166 | text = get_log_text(kernel) 167 | assert text.count('Display Data') == 1 168 | 169 | # With output 170 | code = """ 171 | set terminal pncairo 172 | set output 'multiplot-sin-cos.png' 173 | set multiplot layout 2, 1 174 | plot sin(x) 175 | plot cos(x) 176 | unset multiplot 177 | unset output 178 | """ 179 | kernel.do_execute(code) 180 | assert Path('multiplot-sin-cos.png').exists() 181 | remove_files('multiplot-sin-cos.png') 182 | 183 | 184 | def test_help(): 185 | kernel = get_kernel(GnuplotKernel) 186 | 187 | # The help commands should not get 188 | # stuck in pagers. 189 | 190 | # Fancy notebook help 191 | code = 'terminal?' 192 | kernel.do_execute(code) 193 | text = get_log_text(kernel).lower() 194 | assert 'subtopic' in text 195 | clear_log_text(kernel) 196 | 197 | # help by gnuplot statement 198 | code = 'help print' 199 | kernel.do_execute(code) 200 | text = get_log_text(kernel).lower() 201 | assert 'syntax' in text 202 | clear_log_text(kernel) 203 | 204 | 205 | def test_badinput(): 206 | kernel = get_kernel(GnuplotKernel) 207 | 208 | # No code that endswith a backslash 209 | code = 'plot sin(x),\\' 210 | kernel.do_execute(code) 211 | text = get_log_text(kernel) 212 | assert 'backslash' in text 213 | 214 | 215 | def test_gnuplot_error_message(): 216 | kernel = get_kernel(GnuplotKernel) 217 | 218 | # The error messages gets to the kernel 219 | code = 'plot [1,2][] sin(x)' 220 | kernel.do_execute(code) 221 | text = get_log_text(kernel) 222 | assert ' ^' in text 223 | 224 | 225 | def test_bad_prompt(): 226 | kernel = get_kernel(GnuplotKernel) 227 | # Anything other than 'gnuplot> ' 228 | # is a bad prompt 229 | code = 'set multiplot' 230 | kernel.do_execute(code) 231 | text = get_log_text(kernel) 232 | assert 'warning' in text.lower() 233 | 234 | 235 | def test_data_block(): 236 | kernel = get_kernel(GnuplotKernel) 237 | 238 | # Good data block 239 | code = """ 240 | $DATA << EOD 241 | # x y 242 | 1 1 243 | 2 2 244 | 3 3 245 | 4 4 246 | EOD 247 | plot $DATA 248 | """ 249 | kernel.do_execute(code) 250 | text = get_log_text(kernel) 251 | assert text.count('Display Data') == 1 252 | clear_log_text(kernel) 253 | 254 | # Badly terminated data block 255 | bad_code = """ 256 | $DATA << EOD 257 | # x y 258 | 1 1 259 | 2 2 260 | 3 3 261 | 4 4 262 | EODX 263 | plot $DATA 264 | """ 265 | kernel.do_execute(bad_code) 266 | text = get_log_text(kernel) 267 | assert 'Error' in text 268 | clear_log_text(kernel) 269 | 270 | # Good code should work after the bad_code 271 | kernel.do_execute(code) 272 | text = get_log_text(kernel) 273 | assert text.count('Display Data') == 1 274 | 275 | 276 | def test_do_for_loop(): 277 | kernel = get_kernel(GnuplotKernel) 278 | code = """ 279 | do for [t=0:2] { 280 | plot x**t t sprintf("x^%d",t) 281 | } 282 | """ 283 | kernel.do_execute(code) 284 | text = get_log_text(kernel) 285 | assert text.count('Display Data') == 3 286 | 287 | 288 | # magics # 289 | 290 | def test_cell_magic(): 291 | # To simulate '%load_ext gnuplot_kernel'; 292 | # create a main kernel, a gnuplot kernel and 293 | # a gnuplot magic that uses the gnuplot kernel. 294 | # Then manually register the gnuplot magic into 295 | # the main kernel. 296 | kernel = get_kernel() 297 | gkernel = GnuplotKernel() 298 | gmagic = GnuplotMagic(gkernel) 299 | gkernel.makeSubkernel(kernel) 300 | kernel.line_magics['gnuplot'] = gmagic 301 | kernel.cell_magics['gnuplot'] = gmagic 302 | 303 | # inline output 304 | code = """%%gnuplot 305 | plot cos(x) 306 | """ 307 | kernel.do_execute(code) 308 | assert 'Display Data' in get_log_text(kernel) 309 | clear_log_text(kernel) 310 | 311 | # file output 312 | kernel.call_magic('%gnuplot pngcairo size 560,420') 313 | code = """%%gnuplot 314 | set output 'cosine.png' 315 | plot cos(x) 316 | """ 317 | kernel.do_execute(code) 318 | assert Path('cosine.png').exists() 319 | clear_log_text(kernel) 320 | 321 | remove_files('cosine.png') 322 | 323 | 324 | def test_reset_cell_magic(): 325 | kernel = get_kernel(GnuplotKernel) 326 | 327 | # Use reset statements that have testable effect 328 | code = """%%reset 329 | set output 'sine+cosine.png' 330 | plot sin(x) + cos(x) 331 | """ 332 | kernel.call_magic(code) 333 | assert not Path('sine+cosine.png').exists() 334 | 335 | code = """ 336 | unset key 337 | """ 338 | kernel.do_execute(code) 339 | assert Path('sine+cosine.png').exists() 340 | 341 | remove_files('sine+cosine.png') 342 | 343 | 344 | def test_reset_line_magic(): 345 | kernel = get_kernel(GnuplotKernel) 346 | 347 | # Create a reset 348 | code = """%%reset 349 | set output 'sine+sine.png' 350 | plot sin(x) + sin(x) 351 | """ 352 | kernel.call_magic(code) 353 | 354 | # Remove the reset, execute some code and 355 | # make sure there are no effects 356 | kernel.call_magic('%reset') 357 | code = """ 358 | unset key 359 | """ 360 | kernel.do_execute(code) 361 | assert not Path('sine+sine.png').exists() 362 | 363 | # Bad inline backend 364 | # metakernel messes this exception!! 365 | # with assert_raises(ValueError): 366 | # kernel.call_magic('%gnuplot inline qt') 367 | 368 | 369 | # fixture tests # 370 | def test_remove_files(): 371 | """ 372 | This test create a file. Next test tests that it 373 | is deleted 374 | """ 375 | filename = 'antigravit.txt' 376 | # Create file 377 | # make sure it exis 378 | with open(filename, 'w'): 379 | pass 380 | 381 | assert Path(filename).exists() 382 | 383 | remove_files(filename) 384 | 385 | assert not Path(filename).exists() 386 | -------------------------------------------------------------------------------- /gnuplot_kernel/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Useful functions 3 | """ 4 | 5 | from importlib.metadata import version 6 | 7 | 8 | def get_version(package): 9 | """ 10 | Return the package version 11 | 12 | Raises PackageNotFoundError if package is not installed 13 | """ 14 | # The goal of this function to avoid circular imports if the 15 | # version is required in 2 or more spot before the package has 16 | # been fully installed 17 | return version(package) 18 | -------------------------------------------------------------------------------- /how-to-release.rst: -------------------------------------------------------------------------------- 1 | ############## 2 | How to release 3 | ############## 4 | 5 | Testing 6 | ======= 7 | 8 | * `cd` to the root of project and run 9 | :: 10 | 11 | make test 12 | 13 | * Once all the tests pass move on 14 | 15 | 16 | Tagging 17 | ======= 18 | 19 | * Check out the master branch, open `gnuplot_kernel/kernel.py` 20 | increment the `__version__` string and make a commit. 21 | 22 | * Tag with the version number e.g 23 | :: 24 | 25 | git tag -a v0.1.0 -m 'Version 0.1.0' 26 | 27 | Note the `v` before the version number. 28 | 29 | * Push tag upstream 30 | :: 31 | 32 | git push upstream v0.1.0 33 | 34 | 35 | Packaging 36 | ========= 37 | 38 | * Make sure your `.pypirc` file is setup 39 | `correctly `_. 40 | :: 41 | 42 | cat ~/.pypirc 43 | 44 | 45 | * Build distribution 46 | :: 47 | 48 | make dist 49 | 50 | * (optional) Upload to PyPi test repository 51 | and then try install and test 52 | :: 53 | 54 | make release-test 55 | 56 | mkvirtualenv test-gnuplot-kernel 57 | 58 | pip install -r pypyitest gnuplot_kernel 59 | 60 | cd cdsitepackages 61 | 62 | cd gnuplot_kernel 63 | 64 | nosetests 65 | 66 | cd .. 67 | 68 | deactivate 69 | 70 | rmvirtualenv test-gnuplot-kernel 71 | 72 | 73 | * Upload to PyPi 74 | :: 75 | 76 | make release 77 | 78 | * Done. 79 | -------------------------------------------------------------------------------- /postBuild: -------------------------------------------------------------------------------- 1 | python -m gnuplot_kernel install --user 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Reference https://github.com/pydata/xarray/blob/main/pyproject.toml 2 | [build-system] 3 | requires = [ 4 | "setuptools>=59", 5 | "setuptools_scm[toml]>=6.4", 6 | "wheel", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [tool.setuptools_scm] 11 | fallback_version = "999" 12 | version_scheme = 'post-release' 13 | 14 | # pytest 15 | [tool.pytest.ini_options] 16 | testpaths = [ 17 | "gnuplot_kernel/tests" 18 | ] 19 | addopts = "--pyargs --cov --cov-report=xml --import-mode=importlib" 20 | 21 | # Coverage.py 22 | [tool.coverage.run] 23 | branch = true 24 | source = ["gnuplot_kernel"] 25 | include = ["gnuplot_kernel/*"] 26 | omit = [ 27 | "setup.py", 28 | "gnuplot_kernel/__main__.py" 29 | ] 30 | disable_warnings = ["include-ignored"] 31 | 32 | [tool.coverage.report] 33 | exclude_lines = [ 34 | "pragma: no cover", 35 | "def __repr__", 36 | "if __name__ == .__main__.:", 37 | "def register_ipython_magics", 38 | "def load_ipython_extension" 39 | ] 40 | precision = 1 41 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | # example notebooks 2 | matplotlib 3 | 4 | # Testing 5 | pytest-cov 6 | coveralls 7 | 8 | # Release 9 | wheel 10 | twine 11 | 12 | # Linting 13 | pycodestyle 14 | flake8 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = gnuplot_kernel 3 | description = A gnuplot kernel for Jupyter 4 | url= https://github.com/has2k1/gnuplot_kernel 5 | license = BSD (3-clause) 6 | author = Hassan Kibirige 7 | author_email = has2k1@gmail.com 8 | long_description = file: README.rst 9 | long_description_content_type = text/x-rst 10 | classifiers = 11 | Framework :: IPython 12 | Intended Audience :: End Users/Desktop 13 | Intended Audience :: Science/Research 14 | License :: OSI Approved :: BSD License 15 | Programming Language :: Python :: 3 16 | Topic :: Scientific/Engineering :: Visualization 17 | Topic :: System :: Shells 18 | 19 | project_urls = 20 | Source = https://github.com/has2k1/gnuplot_kernel 21 | Bug Tracker = https://github.com/has2k1/gnuplot_kernel/issues 22 | CI = https://github.com/has2k1/gnuplot_kernel/actions 23 | 24 | [options] 25 | packages = find: 26 | install_requires = 27 | metakernel>=0.29.0 28 | notebook>=6.5.0 29 | python_requires = >=3.8 30 | zip_safe = False 31 | 32 | [options.package_data] 33 | gnuplot.images = *.png 34 | 35 | [options.extras_require] 36 | test = 37 | flake8 38 | pytest-cov 39 | 40 | [bdist_wheel] 41 | 42 | [flake8] 43 | ignore = E121,E123,E126,E226,E24,E704,W503,W504,E741,E743 44 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup() 5 | --------------------------------------------------------------------------------