├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── LuaLaTeX Sample.ipynb ├── MANIFEST.in ├── Makefile ├── Quickstart.ipynb ├── README.rst ├── itikz └── __init__.py ├── requirements_dev.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_itikz.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * itikz version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.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 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDEs 105 | .vscode/ 106 | .idea/ 107 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: python 4 | python: 5 | - 3.6 6 | - 3.5 7 | - 3.4 8 | 9 | install: 10 | - pip install -r requirements_dev.txt 11 | - pip install jupyter jinja2 coveralls cairosvg 12 | - pip install . 13 | - sudo apt-get install -y --no-install-recommends texlive-full pdf2svg 14 | 15 | script: 16 | - "jupyter nbconvert --to html --execute Quickstart.ipynb && ls Quickstart.html" 17 | - pytest --cov=itikz 18 | 19 | after_success: coveralls 20 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * John Bjorn Nelson 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/jbn/itikz/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | itikz could always use more documentation, whether as part of the 42 | official itikz docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/jbn/itikz/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `itikz` for local development. 61 | 62 | 1. Fork the `itikz` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/itikz.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, 68 | this is how you set up your fork for local development:: 69 | 70 | $ mkvirtualenv itikz 71 | $ cd itikz/ 72 | $ pip install -r requirements_dev.txt 73 | $ python setup.py develop 74 | 75 | If you are not using virtualenvwrapper, use Python 3's native virtual environment:: 76 | 77 | $ cd itikz 78 | $ python3 -m venv venv 79 | $ source ./venv/bin/activate 80 | $ pip install -r requirements_dev.txt 81 | $ python setup.py develop 82 | 83 | 4. Create a branch for local development:: 84 | 85 | $ git checkout -b name-of-your-bugfix-or-feature 86 | 87 | Now you can make your changes locally. 88 | 89 | 5. When you're done making changes, check that your changes pass flake8 and the 90 | tests, including testing other Python versions with tox. 91 | 92 | Run flake8 against itikz itself and the tests. 93 | 94 | $ flake8 itikz tests 95 | 96 | Run Python tests with either of these commands:: 97 | $ python setup.py test 98 | $ py.test 99 | 100 | Run tox:: 101 | $ tox 102 | 103 | To get flake8 and tox, just pip install them into your virtualenv 104 | (this will already be done if you installed the requirements_dev.txt packages). 105 | 106 | 6. Commit your changes and push your branch to GitHub:: 107 | 108 | $ git add . 109 | $ git commit -m "Your detailed description of your changes." 110 | $ git push origin name-of-your-bugfix-or-feature 111 | 112 | 7. Submit a pull request through the GitHub website. 113 | 114 | Pull Request Guidelines 115 | ----------------------- 116 | 117 | Before you submit a pull request, check that it meets these guidelines: 118 | 119 | 1. The pull request should include tests. 120 | 2. If the pull request adds functionality, the docs should be updated. Put 121 | your new functionality into a function with a docstring, and add the 122 | feature to the list in README.rst. 123 | 3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check 124 | https://travis-ci.org/jbn/itikz/pull_requests 125 | and make sure that the tests pass for all supported Python versions. 126 | 127 | Tips 128 | ---- 129 | 130 | To run a subset of tests:: 131 | 132 | $ pytest 133 | 134 | 135 | Deploying 136 | --------- 137 | 138 | A reminder for the maintainers on how to deploy. 139 | Make sure all your changes are committed (including an entry in HISTORY.rst). 140 | Then run:: 141 | 142 | $ bumpversion patch # possible: major / minor / patch 143 | $ git push 144 | $ git push --tags 145 | 146 | Travis will then deploy to PyPI if tests pass. 147 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.0.1 (2018-10-12) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | 10 | 0.0.2 (2018-10-13) 11 | ------------------ 12 | 13 | * Emit error messages. 14 | 15 | 0.0.3 (2018-10-16) 16 | ------------------ 17 | 18 | * Add line magic usage 19 | * Change to load_ext pattern 20 | 21 | 0.0.4 (2018-10-16) 22 | ------------------ 23 | 24 | * Add argparse parsing for line and cell magic 25 | * Add --temp-dir option to reduce clutter 26 | * Add --file_prefix for organization 27 | 28 | 0.0.5 (2018-10-16) 29 | ------------------ 30 | 31 | * Add Quickstart notebook as a demo 32 | * Remove print statements introduced during debugging 33 | 34 | 0.0.6 (2018-10-17) 35 | ------------------ 36 | 37 | * Add ITIKZ_TEMP_DIR environmental variable usage 38 | * Add --implicit-pic 39 | 40 | 0.0.7 (2018-10-17) 41 | ------------------ 42 | 43 | * Add --scale arg 44 | * Add --tikz-libraries 45 | * Add --tex-packages 46 | * Add --implicit-standalone 47 | 48 | 0.0.8 (2018-10-17) 49 | ------------------ 50 | 51 | * Add jinja2 templating 52 | * Add jinja2 interpolated code debug printing 53 | 54 | 0.1.0 (2018-10-21) 55 | ------------------ 56 | 57 | * Add unit tests 58 | * Fix cleanup bug 59 | 60 | 0.1.1 (2018-10-24) 61 | ------------------ 62 | 63 | * Add --rasterize option 64 | 65 | 0.1.2 (2018-10-25) 66 | ------------------ 67 | 68 | * Change default to only show the training 10 lines of an error 69 | * Add --full-error option for full emitted error 70 | 71 | 0.1.3 (2018-10-29) 72 | ------------------ 73 | 74 | * Change default to only show the training 20 lines of an error 75 | 76 | 0.1.4 (2019-09-27) 77 | ------------------ 78 | 79 | * Add option to set LaTeX program (e.g. lualatex) via PR from 80 | Tom Nurkkala (thanks, Tom!) 81 | 82 | 0.1.5 (2020-01-04) 83 | ------------------ 84 | 85 | * Fix bug with tilde expansion on windows via PR rom asteppke 86 | on github (thanks, asteppke!) 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018, John Bjorn Nelson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /LuaLaTeX Sample.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 3, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "The itikz extension is already loaded. To reload it, use:\n", 13 | " %reload_ext itikz\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "%load_ext itikz" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 4, 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "%reload_ext itikz" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 5, 33 | "metadata": {}, 34 | "outputs": [ 35 | { 36 | "data": { 37 | "image/svg+xml": [ 38 | "\n", 39 | "\n", 40 | "\n", 41 | "\n", 42 | "\n", 43 | "\n", 44 | "\n", 45 | "\n", 46 | "\n", 47 | "\n", 48 | "\n", 49 | "\n", 50 | "\n", 51 | "\n", 52 | "\n", 53 | "\n", 54 | "\n", 55 | "\n", 56 | "\n", 57 | "\n", 58 | "\n", 59 | "\n", 60 | "\n", 61 | "\n", 62 | "\n", 63 | "\n", 64 | "\n", 65 | "\n", 66 | "\n", 67 | "\n", 68 | "\n", 69 | "\n", 70 | "\n", 71 | "\n", 72 | "\n", 73 | "\n", 74 | "\n", 75 | "\n", 76 | "\n", 77 | "\n", 78 | "\n", 79 | "\n", 80 | "\n", 81 | "\n", 82 | "\n", 83 | "\n", 84 | "\n", 85 | "\n", 86 | "\n", 87 | "\n", 88 | "\n", 89 | "\n", 90 | "\n", 91 | "\n", 92 | "\n", 93 | "\n", 94 | "\n", 95 | "\n", 96 | "\n", 97 | "\n", 98 | "\n", 99 | "\n", 100 | "\n", 101 | "\n", 102 | "\n", 103 | "\n", 104 | "\n", 105 | "\n", 106 | "\n", 107 | "\n", 108 | "\n", 109 | "\n", 110 | "\n", 111 | "\n", 112 | "\n", 113 | "\n", 114 | "\n", 115 | "\n", 116 | "\n", 117 | "\n", 118 | "\n", 119 | "\n", 120 | "\n", 121 | "\n", 122 | "\n", 123 | "\n", 124 | "\n", 125 | "\n", 126 | "\n", 127 | "\n", 128 | "\n", 129 | "\n", 130 | "\n", 131 | "\n", 132 | "\n", 133 | "\n", 134 | "\n", 135 | "\n", 136 | "\n", 137 | "\n", 138 | "\n", 139 | "\n", 140 | "\n", 141 | "\n", 142 | "\n", 143 | "\n", 144 | "\n", 145 | "\n", 146 | "\n", 147 | "\n", 148 | "\n", 149 | "\n", 150 | "\n", 151 | "\n", 152 | "\n", 153 | "\n", 154 | "\n", 155 | "\n", 156 | "\n", 157 | "\n", 158 | "\n", 159 | "\n", 160 | "\n", 161 | "\n", 162 | "\n", 163 | "\n", 164 | "\n", 165 | "\n", 166 | "\n", 167 | "\n", 168 | "\n", 169 | "\n", 170 | "\n", 171 | "\n", 172 | "\n", 173 | "\n", 174 | "\n", 175 | "\n", 176 | "\n", 177 | "\n", 178 | "\n", 179 | "\n", 180 | "\n", 181 | "\n", 182 | "\n", 183 | "\n", 184 | "\n", 185 | "\n", 186 | "\n", 187 | "\n", 188 | "\n", 189 | "\n", 190 | "\n", 191 | "\n", 192 | "\n", 193 | " \n", 194 | "\n", 195 | "\n", 196 | " \n", 197 | "\n", 198 | "\n", 199 | " \n", 200 | "\n", 201 | "\n", 202 | " \n", 203 | "\n", 204 | "\n", 205 | "\n", 206 | "\n", 207 | "\n", 208 | "\n", 209 | "\n", 210 | "\n", 211 | "\n", 212 | "\n", 213 | "\n", 214 | "\n", 215 | "\n", 216 | "\n", 217 | "\n", 218 | "\n", 219 | "\n", 220 | "\n", 221 | "\n", 222 | "\n", 223 | "\n", 224 | "\n", 225 | "\n", 226 | "\n", 227 | "\n", 228 | "\n", 229 | "\n", 230 | "\n", 231 | "\n", 232 | "\n", 233 | "\n", 234 | "\n", 235 | "\n", 236 | "\n", 237 | "\n", 238 | "\n", 239 | "\n", 240 | "\n", 241 | "\n", 242 | "\n", 243 | "\n", 244 | "\n", 245 | "\n", 246 | "\n", 247 | "\n", 248 | "\n", 249 | "\n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | "\n", 256 | "\n", 257 | "\n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | "\n", 264 | "\n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | "\n", 273 | "\n", 274 | "\n", 275 | " \n", 276 | " \n", 277 | "\n", 278 | "\n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | "\n", 285 | "\n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | "\n", 297 | "\n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | "\n", 306 | "\n", 307 | "\n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | "\n", 317 | "\n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | "\n", 324 | "\n", 325 | "\n", 326 | " \n", 327 | " \n", 328 | "\n", 329 | "\n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | "\n", 334 | "\n", 335 | " \n", 336 | " \n", 337 | "\n", 338 | "\n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | "\n", 352 | "\n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | "\n", 361 | "\n", 362 | "\n", 363 | "\n", 364 | "\n", 365 | "\n", 366 | " \n", 367 | " \n", 368 | "\n", 369 | "\n", 370 | " \n", 371 | "\n", 372 | "\n", 373 | " \n", 374 | "\n", 375 | "\n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | "\n", 382 | "\n", 383 | "\n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | "\n", 392 | "\n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | "\n", 397 | "\n", 398 | "\n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | "\n", 408 | "\n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | "\n", 413 | "\n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | "\n", 418 | "\n", 419 | "\n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | "\n", 428 | "\n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | "\n", 434 | "\n", 435 | " \n", 436 | " \n", 437 | "\n", 438 | "\n", 439 | "\n", 440 | " \n", 441 | "\n", 442 | "\n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | "\n", 453 | "\n", 454 | "\n", 455 | " \n", 456 | "\n", 457 | "\n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | "\n", 465 | "\n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | "\n", 475 | "\n", 476 | "\n", 477 | "\n", 478 | "\n", 479 | " \n", 480 | "\n", 481 | "\n", 482 | " \n", 483 | " \n", 484 | "\n", 485 | "\n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | "\n", 494 | "\n", 495 | "\n", 496 | " \n", 497 | "\n", 498 | "\n", 499 | " \n", 500 | " \n", 501 | "\n", 502 | "\n", 503 | "\n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | "\n", 509 | "\n", 510 | "\n", 511 | "\n", 512 | "\n", 513 | " \n", 514 | "\n", 515 | "\n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | "\n", 523 | "\n", 524 | "\n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | "\n", 531 | "\n", 532 | "\n", 533 | " \n", 534 | "\n", 535 | "\n", 536 | "\n", 537 | " \n", 538 | "\n", 539 | "\n", 540 | "\n", 541 | " \n", 542 | "\n", 543 | "\n", 544 | "\n", 545 | " \n", 546 | "\n", 547 | "\n", 548 | "\n", 549 | " \n", 550 | "\n", 551 | "\n", 552 | "\n", 553 | " \n", 554 | "\n", 555 | "\n", 556 | "\n", 557 | " \n", 558 | "\n", 559 | "\n", 560 | "\n", 561 | "\n", 562 | "\n", 563 | " \n", 564 | "\n", 565 | "\n", 566 | "\n", 567 | " \n", 568 | "\n", 569 | "\n", 570 | "" 571 | ], 572 | "text/plain": [ 573 | "" 574 | ] 575 | }, 576 | "execution_count": 5, 577 | "metadata": {}, 578 | "output_type": "execute_result" 579 | } 580 | ], 581 | "source": [ 582 | "%%itikz --tex-program=lualatex\n", 583 | "\\documentclass{standalone}\n", 584 | "\n", 585 | "\\usepackage{tikz}\n", 586 | "\n", 587 | "\\usetikzlibrary{arrows.meta}\n", 588 | "\\usetikzlibrary{calc}\n", 589 | "\\usetikzlibrary{fit}\n", 590 | "\\usetikzlibrary{graphs}\n", 591 | "\\usetikzlibrary{matrix}\n", 592 | "\\usetikzlibrary{positioning}\n", 593 | "\n", 594 | "\\usetikzlibrary{graphdrawing}\n", 595 | "\\usegdlibrary{layered}\n", 596 | "\n", 597 | "\\begin{document}\n", 598 | " \\begin{tikzpicture}[rounded corners,thick,\n", 599 | " app/.style={draw=red,fill=red!20},\n", 600 | " server/.style={draw=green,fill=green!20},\n", 601 | " dbms/.style={draw=blue,fill=blue!20},\n", 602 | " marble/.style={text=white,circle,font=\\footnotesize,fill=#1!75!black}]\n", 603 | " \\graph [\n", 604 | " layered layout,\n", 605 | " level sep=0.75cm,sibling sep=0.5cm,\n", 606 | " nodes={minimum height=1.5cm,minimum width=2.5cm,draw,align=center},\n", 607 | " edges={arrows={-Stealth}}\n", 608 | " ] {\n", 609 | " RDBMS[dbms]\n", 610 | " -> QB/Query\\\\Builder[dbms]\n", 611 | " -> ORM/\"Object-\\\\Relational\\\\Mapping\"[dbms]\n", 612 | " -> BL/Business\\\\Logic[app];\n", 613 | " RDBMS -> ERD/\"Entity-\\\\Relationship\\\\Diagram\"[dbms];\n", 614 | " { UI/UI Toolkit[app], BL, API/RESTful\\\\API[server] } -> SPA/\"Single-Page\\\\App\"[app];\n", 615 | " BL -> Server/RESTful\\\\Server[server];\n", 616 | " Validation[server] -> API;\n", 617 | " { API, Auth/\"Authen-\\\\tication\"[server] } -> Server;\n", 618 | "\n", 619 | " { [nodes={gray,dashed,minimum height=0cm,minimum width=0cm},\n", 620 | " edges={gray,dotted}]\n", 621 | " TypeScript -> Vue;\n", 622 | " Hapi -> Server;\n", 623 | " Vue -> UI;\n", 624 | " { Server, SPA } -> Testing;\n", 625 | " Async -> { QB, ORM, BL, Auth, Validation };\n", 626 | " };\n", 627 | " };\n", 628 | "\n", 629 | " \\node[marble=blue] at (RDBMS.north east) {1};\n", 630 | " \\node[marble=blue] at (ERD.north east) {2};\n", 631 | " \\node[marble=blue] at (QB.north east) {3};\n", 632 | " \\node[marble=blue] at (ORM.north east) {4};\n", 633 | " \\node[marble=green] at (API.north east) {5};\n", 634 | " \\node[marble=green] at (Server.south) {6};\n", 635 | " \\node[marble=green] at (Validation.north east) {7};\n", 636 | " \\node[marble=red] at (UI.north east) {8};\n", 637 | " \\node[marble=red] at (SPA.south) {9};\n", 638 | "\n", 639 | " \\end{tikzpicture}\n", 640 | "\\end{document}" 641 | ] 642 | }, 643 | { 644 | "cell_type": "code", 645 | "execution_count": null, 646 | "metadata": {}, 647 | "outputs": [], 648 | "source": [] 649 | } 650 | ], 651 | "metadata": { 652 | "kernelspec": { 653 | "display_name": "Python 3", 654 | "language": "python", 655 | "name": "python3" 656 | }, 657 | "language_info": { 658 | "codemirror_mode": { 659 | "name": "ipython", 660 | "version": 3 661 | }, 662 | "file_extension": ".py", 663 | "mimetype": "text/x-python", 664 | "name": "python", 665 | "nbconvert_exporter": "python", 666 | "pygments_lexer": "ipython3", 667 | "version": "3.7.3" 668 | } 669 | }, 670 | "nbformat": 4, 671 | "nbformat_minor": 4 672 | } 673 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 itikz tests 55 | 56 | test: ## run tests quickly with the default Python 57 | py.test 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source itikz -m pytest 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | release: dist ## package and upload a release 69 | twine upload dist/* 70 | 71 | dist: clean ## builds source and wheel package 72 | python setup.py sdist 73 | python setup.py bdist_wheel 74 | ls -l dist 75 | 76 | install: clean ## install the package to the active Python's site-packages 77 | python setup.py install 78 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | itikz 3 | ===== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/itikz.svg 7 | :target: https://pypi.python.org/pypi/itikz 8 | 9 | .. image:: https://travis-ci.org/jbn/itikz.svg?branch=master 10 | :target: https://travis-ci.org/jbn/itikz 11 | 12 | .. image:: https://img.shields.io/coveralls/github/jbn/itikz.svg 13 | :target: https://coveralls.io/github/jbn/itikz 14 | 15 | Cell magic for PGF/TikZ-to-SVG rendering in Jupyter 16 | 17 | * Free software: MIT license 18 | 19 | Basic Usage 20 | ----------- 21 | 22 | Install it: 23 | 24 | .. code:: sh 25 | 26 | pip install itikz 27 | 28 | Load it: 29 | 30 | .. code:: python 31 | 32 | %load_ext itikz 33 | 34 | Use it: 35 | 36 | .. code:: tex 37 | 38 | %%itikz --file-prefix implicit-demo- --implicit-pic 39 | \draw[help lines] grid (5, 5); 40 | \draw[fill=magenta!10] (1, 1) rectangle (2, 2); 41 | \draw[fill=magenta!10] (2, 1) rectangle (3, 2); 42 | \draw[fill=magenta!10] (3, 1) rectangle (4, 2); 43 | \draw[fill=magenta!10] (3, 2) rectangle (4, 3); 44 | \draw[fill=magenta!10] (2, 3) rectangle (3, 4); 45 | 46 | Getting Started Guide 47 | --------------------- 48 | 49 | `Getting Started Notebook `__ 50 | -------------------------------------------------------------------------------- /itikz/__init__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import shlex 4 | import sys 5 | import tempfile 6 | from hashlib import md5 7 | from shutil import fnmatch 8 | from string import Template 9 | from subprocess import check_output, CalledProcessError 10 | from IPython.display import SVG, Image 11 | from IPython.core.magic import Magics, magics_class, line_cell_magic 12 | 13 | 14 | __author__ = """John Bjorn Nelson""" 15 | __email__ = 'jbn@abreka.com' 16 | __version__ = '0.1.5' 17 | 18 | 19 | IMPLICIT_PIC_TMPL = Template(r"""\documentclass[tikz]{standalone} 20 | $extras 21 | \begin{document} 22 | \begin{tikzpicture}[scale=$scale] 23 | $src 24 | \end{tikzpicture} 25 | \end{document}""") 26 | 27 | 28 | IMPLICIT_STANDALONE = Template(r"""\documentclass{standalone} 29 | $extras 30 | \begin{document} 31 | $src 32 | \end{document}""") 33 | 34 | 35 | JINJA2_ENABLED = True 36 | try: 37 | import jinja2 38 | except ImportError: # pragma: no cover 39 | JINJA2_ENABLED = False 40 | 41 | 42 | CAIROSVG_ENABLED = True 43 | try: 44 | import cairosvg 45 | except ImportError: # pragma: no cover 46 | CAIROSVG_ENABLED = False 47 | 48 | 49 | def parse_args(line): 50 | parser = argparse.ArgumentParser(prog="%%itikz", 51 | description='Tikz to tex to SVG', 52 | add_help=False) 53 | 54 | parser.add_argument('k', type=str, nargs='?', 55 | help='the variable in IPython with the string source') 56 | 57 | parser.add_argument('--temp-dir', dest='temp_dir', action='store_true', 58 | default=False, 59 | help='emit artifacts to system temp dir') 60 | 61 | parser.add_argument('--file-prefix', dest='file_prefix', 62 | default='', help='emit artifacts with a path prefix') 63 | 64 | parser.add_argument('--implicit-pic', dest='implicit_pic', 65 | action='store_true', default=False, 66 | help='wrap source in implicit tikzpicture document') 67 | 68 | parser.add_argument('--implicit-standalone', dest='implicit_standalone', 69 | action='store_true', default=False, 70 | help='wrap source in implicit document') 71 | 72 | parser.add_argument('--scale', dest='scale', 73 | default='1', 74 | help='Set tikzpicture scale in --implicit-pic tmpl') 75 | 76 | parser.add_argument('--tikz-libraries', dest='tikz_libraries', 77 | default='', 78 | help='Comma separated list of tikz libraries to use') 79 | 80 | parser.add_argument('--tex-packages', dest='tex_packages', 81 | default='', 82 | help='Comma separated list of tex packages to use') 83 | 84 | parser.add_argument('--tex-program', dest='tex_program', 85 | default='pdflatex', 86 | help='Name of alternate LaTeX program (e.g., lualatex)') 87 | 88 | parser.add_argument('--as-jinja', dest='as_jinja', 89 | action='store_true', default=False, 90 | help="Interpret the source as a jinja2 template") 91 | 92 | parser.add_argument('--print-jinja', dest='print_jinja', 93 | action='store_true', default=False, 94 | help="Print interpolated jinja2 source then bail.") 95 | 96 | parser.add_argument('--rasterize', dest='rasterize', 97 | action='store_true', default=False, 98 | help="Rasterize the svg with cairosvg") 99 | 100 | parser.add_argument('--full-error', dest='full_err', 101 | action='store_true', default=False, 102 | help="Emit the full error message") 103 | 104 | # Override help: the default does a sys.exit() 105 | parser.add_argument('-h', '--help', dest='print_help', 106 | action='store_true', default=False, 107 | help='show this help message') 108 | 109 | return parser, parser.parse_args(shlex.split(line)) 110 | 111 | 112 | def get_cwd(args): 113 | if args.temp_dir or os.environ.get('ITIKZ_TEMP_DIR'): 114 | cwd = os.path.join(tempfile.gettempdir(), 'itikz') 115 | os.makedirs(cwd, exist_ok=True) 116 | return cwd # Override as cwd 117 | else: 118 | return None # No override. 119 | 120 | 121 | def fetch_or_compile_svg(src, prefix='', working_dir=None, full_err=False, tex_program='pdflatex'): 122 | src_hash = md5(src.encode()).hexdigest() 123 | output_path = prefix + src_hash 124 | if working_dir is not None: 125 | output_path = os.path.join(working_dir, output_path) 126 | svg_path = output_path + ".svg" 127 | 128 | if not os.path.exists(svg_path): 129 | tex_path = output_path + ".tex" 130 | pdf_path = output_path + ".pdf" 131 | tex_filename = os.path.basename(tex_path) 132 | 133 | with open(tex_path, "w") as fp: 134 | fp.write(src) 135 | 136 | try: 137 | check_output([tex_program, tex_filename], cwd=working_dir) 138 | check_output(["pdf2svg", pdf_path, svg_path], cwd=working_dir) 139 | except CalledProcessError as e: 140 | cleanup_artifacts(working_dir, src_hash) 141 | err_msg = e.output.decode() 142 | 143 | if not full_err: # tail -n 20 144 | err_msg = "\n".join(err_msg.splitlines()[-20:]) 145 | 146 | print(err_msg, file=sys.stderr) 147 | return 148 | 149 | cleanup_artifacts(working_dir, src_hash, svg_path, tex_path) 150 | 151 | with open(svg_path, "r") as fp: 152 | return SVG(fp.read()) 153 | 154 | 155 | def cleanup_artifacts(working_dir, src_hash, *retaining): 156 | glob = "*{}*".format(src_hash) 157 | 158 | for file_name in fnmatch.filter(os.listdir(working_dir), glob): 159 | file_path = os.path.join(working_dir or '', file_name) 160 | if file_path not in retaining: 161 | os.unlink(file_path) 162 | 163 | 164 | def load_and_interpolate_jinja2(src, ns): 165 | # The FileSystemLoader should operate in the current working directory. 166 | # By assumption, extended jinja templates aren't temporary files -- 167 | # the user wrote them by hand. They are part of code you would want in 168 | # your repository! 169 | fs_loader = jinja2.FileSystemLoader(os.getcwd()) 170 | tmpl_env = jinja2.Environment(loader=fs_loader) 171 | 172 | # The final template -- the one that may extend a custom template -- 173 | # may be in the current directory or in a temporary one. So, it's 174 | # passed as a string. 175 | tmpl = tmpl_env.from_string(src) 176 | 177 | return tmpl.render(**ns) 178 | 179 | 180 | @magics_class 181 | class TikZMagics(Magics): 182 | 183 | @line_cell_magic 184 | def itikz(self, line, cell=None): 185 | src = cell 186 | parser, args = parse_args(line) 187 | 188 | if args.print_help: 189 | parser.print_help(file=sys.stderr) 190 | return 191 | 192 | ipython_ns = self.shell.user_ns 193 | 194 | if cell is None: 195 | 196 | if args.k is None or args.k not in ipython_ns: 197 | parser.print_usage(file=sys.stderr) 198 | return 199 | 200 | src = ipython_ns[args.k] 201 | 202 | if args.implicit_pic and args.implicit_standalone: 203 | print("Can't use --implicit-standalone and --implicit-pic", 204 | file=sys.stderr) 205 | return None 206 | 207 | # Jinja processing comes BEFORE implicit pic or standalone processing! 208 | if args.as_jinja: 209 | 210 | if not JINJA2_ENABLED: 211 | print("Please install jinja2", file=sys.stderr) 212 | print("$ pip install jinja2", file=sys.stderr) 213 | return 214 | 215 | src = load_and_interpolate_jinja2(src, ipython_ns) 216 | 217 | if args.print_jinja: 218 | print(src) 219 | return 220 | 221 | if args.implicit_pic: 222 | src = IMPLICIT_PIC_TMPL.substitute(build_template_args(src, args)) 223 | elif args.implicit_standalone: 224 | tmpl_args = build_template_args(src, args) 225 | src = IMPLICIT_STANDALONE.substitute(tmpl_args) 226 | 227 | svg = fetch_or_compile_svg(src, args.file_prefix, get_cwd(args), 228 | args.full_err, args.tex_program) 229 | 230 | if svg is None: 231 | return None 232 | 233 | if args.rasterize: 234 | if not CAIROSVG_ENABLED: 235 | print("Please install cairosvg", file=sys.stderr) 236 | print("$ pip install cairosvg", file=sys.stderr) 237 | return 238 | 239 | png_bytes = cairosvg.svg2png(bytestring=svg.data.encode()) 240 | 241 | return Image(data=png_bytes) 242 | 243 | return svg 244 | 245 | 246 | def build_template_args(src, args): 247 | extras = [] 248 | 249 | if args.tex_packages: 250 | extras.append(r"\usepackage{" + args.tex_packages + "}") 251 | 252 | if args.tikz_libraries: 253 | extras.append(r"\usetikzlibrary{" + args.tikz_libraries + "}") 254 | 255 | extras = "\n".join(extras) 256 | 257 | return dict(src=src, 258 | scale=float(args.scale), 259 | extras=extras) 260 | 261 | 262 | def load_ipython_extension(ipython): # pragma: no cover 263 | ipython.register_magics(TikZMagics) 264 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==9.0.1 2 | bumpversion==0.5.3 3 | wheel==0.30.0 4 | watchdog==0.8.3 5 | flake8==3.5.0 6 | tox==2.9.1 7 | coverage==4.5.1 8 | Sphinx==1.7.1 9 | twine==1.10.0 10 | 11 | pytest 12 | pytest-runner 13 | pytest-mock 14 | pytest-cov 15 | IPython 16 | cairosvg 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.5 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:itikz/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | # Define setup.py command aliases here 22 | test = pytest 23 | 24 | [tool:pytest] 25 | collect_ignore = ['setup.py'] 26 | 27 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | 8 | with open('README.rst') as readme_file: 9 | readme = readme_file.read() 10 | 11 | with open('HISTORY.rst') as history_file: 12 | history = history_file.read() 13 | 14 | requirements = [] 15 | 16 | setup_requirements = ['pytest-runner', ] 17 | 18 | test_requirements = ['pytest', ] 19 | 20 | setup( 21 | author="John Bjorn Nelson", 22 | author_email='jbn@abreka.com', 23 | classifiers=[ 24 | 'Development Status :: 2 - Pre-Alpha', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Natural Language :: English', 28 | "Programming Language :: Python :: 2", 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3', 31 | 'Programming Language :: Python :: 3.4', 32 | 'Programming Language :: Python :: 3.5', 33 | 'Programming Language :: Python :: 3.6', 34 | 'Programming Language :: Python :: 3.7', 35 | ], 36 | description="Cell magic for PGF/TikZ-to-SVG rendering in Jupyter", 37 | entry_points={ 38 | 'console_scripts': [ 39 | 'itikz=itikz.cli:main', 40 | ], 41 | }, 42 | install_requires=requirements, 43 | license="MIT license", 44 | long_description=readme + '\n\n' + history, 45 | include_package_data=True, 46 | keywords='itikz', 47 | name='itikz', 48 | packages=find_packages(include=['itikz']), 49 | setup_requires=setup_requirements, 50 | test_suite='tests', 51 | tests_require=test_requirements, 52 | url='https://github.com/jbn/itikz', 53 | version='0.1.5', 54 | zip_safe=False, 55 | ) 56 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbn/itikz/e5dc8e07b2da064bfc4a6ea2dc7e32df8ca4b21d/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_itikz.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import itikz 4 | import tempfile 5 | from IPython.display import SVG, Image 6 | 7 | 8 | @pytest.mark.skipif(not itikz.JINJA2_ENABLED, reason="jinja2 not installed") 9 | def test_load_and_interpolate_jinja2(monkeypatch, tmpdir): 10 | # Create the parent_tmpl.md template. 11 | monkeypatch.chdir(tmpdir) 12 | parent_src = """# Example\n{% block content -%}{% endblock -%}""" 13 | parent = tmpdir / "parent_tmpl.md" 14 | parent.write(parent_src) 15 | 16 | # Use it. 17 | pkg_name = "itikz" 18 | child_src = ('{% extends "parent_tmpl.md" %}' 19 | "{% block content -%}{{pkg_name}}{% endblock -%}""") 20 | output = itikz.load_and_interpolate_jinja2(child_src, locals()) 21 | 22 | assert output == "# Example\nitikz" 23 | 24 | 25 | def test_get_cwd(mocker, monkeypatch): 26 | args = mocker.MagicMock() 27 | 28 | # Case 1: No temp_dir implies the no (`None`) overriding. 29 | args.temp_dir = False 30 | assert itikz.get_cwd(args) is None 31 | 32 | # Case 2: The environmental variable demands a temp_dir. 33 | monkeypatch.setenv('ITIKZ_TEMP_DIR', '1') 34 | assert itikz.get_cwd(args).endswith("itikz") 35 | monkeypatch.delenv('ITIKZ_TEMP_DIR') 36 | 37 | # Case 2: Use the system temp_dir. 38 | args.temp_dir = True 39 | assert itikz.get_cwd(args).endswith("itikz") 40 | 41 | 42 | def test_parse_args_does_not_sysexit(capsys): 43 | itikz.parse_args("-h") 44 | _, err = capsys.readouterr() 45 | assert err == "" 46 | 47 | 48 | def test_build_template_args(mocker): 49 | 50 | # Case 1: No overrides. 51 | args = itikz.parse_args("")[1] 52 | res = itikz.build_template_args("code", args) 53 | assert res == dict(src="code", scale=1, extras="") 54 | 55 | # Case 2: Override scale. 56 | args = itikz.parse_args("--scale 2")[1] 57 | res = itikz.build_template_args("code", args) 58 | assert res == dict(src="code", scale=2.0, extras="") 59 | 60 | # Case 3: Override tex packages. 61 | args = itikz.parse_args("--scale 2 --tex-packages a,b")[1] 62 | res = itikz.build_template_args("code", args) 63 | assert res == dict(src="code", scale=2.0, extras=r"\usepackage{a,b}") 64 | 65 | # Case 4: Override tikz libraries. 66 | invocation = "--scale 2 --tex-packages a,b --tikz-libraries c,d" 67 | args = itikz.parse_args(invocation)[1] 68 | res = itikz.build_template_args("code", args) 69 | assert res == dict(src="code", scale=2.0, 70 | extras="\\usepackage{a,b}\n\\usetikzlibrary{c,d}") 71 | 72 | 73 | RECTANGLE_TIKZ = r""" 74 | \documentclass[tikz]{standalone} 75 | \begin{document} 76 | \begin{tikzpicture} 77 | \draw[fill=blue] (0, 0) rectangle (1, 1); 78 | \end{tikzpicture} 79 | \end{document} 80 | """.strip() 81 | 82 | 83 | BAD_TIKZ = "HELLO WORLD" 84 | 85 | 86 | def test_fetch_or_compile_svg_good_input(tmpdir, monkeypatch): 87 | expected_md5 = "15d53b05d3a27e1545c9a3688be5e3b4" 88 | res = itikz.fetch_or_compile_svg(RECTANGLE_TIKZ, 'test_', str(tmpdir)) 89 | 90 | for ext in 'tex', 'svg': 91 | path = tmpdir.join("test_{}.{}".format(expected_md5, ext)) 92 | assert os.path.exists(str(path)) 93 | 94 | for ext in 'log', 'aux', 'pdf': 95 | path = tmpdir.join("test_{}.{}".format(expected_md5, ext)) 96 | assert not os.path.exists(str(path)) 97 | 98 | assert isinstance(res, SVG) 99 | 100 | 101 | def test_fetch_or_compile_svg_bad_input(tmpdir, capsys): 102 | expected_md5 = "361fadf1c712e812d198c4cab5712a79" 103 | res = itikz.fetch_or_compile_svg(BAD_TIKZ, 'test_', str(tmpdir)) 104 | 105 | for ext in 'tex', 'svg', 'log', 'aux', 'pdf': 106 | path = tmpdir.join("test_{}.{}".format(expected_md5, ext)) 107 | assert not os.path.exists(str(path)) 108 | 109 | assert res is None 110 | 111 | _, err = capsys.readouterr() 112 | assert 'error' in err.lower() 113 | assert len(err.splitlines()) == 20 114 | 115 | 116 | def test_fetch_or_compile_svg_bad_input_full_err(tmpdir, capsys): 117 | expected_md5 = "361fadf1c712e812d198c4cab5712a79" 118 | res = itikz.fetch_or_compile_svg(BAD_TIKZ, 'test_', str(tmpdir), True) 119 | 120 | for ext in 'tex', 'svg', 'log', 'aux', 'pdf': 121 | path = tmpdir.join("test_{}.{}".format(expected_md5, ext)) 122 | assert not os.path.exists(str(path)) 123 | 124 | assert res is None 125 | 126 | _, err = capsys.readouterr() 127 | assert 'error' in err.lower() 128 | assert len(err.splitlines()) > 20 129 | 130 | 131 | @pytest.fixture 132 | def itikz_magic(mocker, monkeypatch): 133 | obj = itikz.TikZMagics() 134 | shell = mocker.MagicMock() 135 | shell.user_ns = {} 136 | monkeypatch.setattr(obj, 'shell', shell) 137 | return obj 138 | 139 | 140 | def test_magic_print_help(itikz_magic, capsys): 141 | assert itikz_magic.itikz("--help") is None 142 | _, err = capsys.readouterr() 143 | assert err.startswith("usage: %%itikz") 144 | 145 | 146 | def test_magic_print_help_on_no_input(itikz_magic, capsys): 147 | assert itikz_magic.itikz('') is None 148 | _, err = capsys.readouterr() 149 | assert err.startswith("usage: %%itikz") 150 | 151 | 152 | def test_magic_cell_usage(itikz_magic): 153 | expected_md5 = "15d53b05d3a27e1545c9a3688be5e3b4" 154 | tmp_dir = os.path.join(tempfile.gettempdir(), 'itikz') 155 | 156 | res = itikz_magic.itikz("--temp-dir --file-prefix test_", RECTANGLE_TIKZ) 157 | 158 | for ext in 'tex', 'svg': 159 | path = os.path.join(tmp_dir, "test_{}.{}".format(expected_md5, ext)) 160 | assert os.path.exists(str(path)) 161 | 162 | for ext in 'log', 'aux', 'pdf': 163 | path = os.path.join(tmp_dir, "test_{}.{}".format(expected_md5, ext)) 164 | assert not os.path.exists(str(path)) 165 | 166 | assert isinstance(res, SVG) 167 | 168 | 169 | def test_magic_line_usage(itikz_magic): 170 | expected_md5 = "15d53b05d3a27e1545c9a3688be5e3b4" 171 | tmp_dir = os.path.join(tempfile.gettempdir(), 'itikz') 172 | itikz_magic.shell.user_ns['env_src'] = RECTANGLE_TIKZ 173 | 174 | res = itikz_magic.itikz("--temp-dir --file-prefix test_ env_src") 175 | 176 | for ext in 'tex', 'svg': 177 | path = os.path.join(tmp_dir, "test_{}.{}".format(expected_md5, ext)) 178 | assert os.path.exists(str(path)) 179 | 180 | for ext in 'log', 'aux', 'pdf': 181 | path = os.path.join(tmp_dir, "test_{}.{}".format(expected_md5, ext)) 182 | assert not os.path.exists(str(path)) 183 | 184 | assert isinstance(res, SVG) 185 | 186 | 187 | def test_magic_no_simultanous_standalone_and_pic(itikz_magic, capsys): 188 | res = itikz_magic.itikz("--implicit-pic --implicit-standalone", 189 | RECTANGLE_TIKZ) 190 | 191 | assert res is None 192 | _, err = capsys.readouterr() 193 | assert err.startswith("Can't use --implicit") 194 | 195 | 196 | def test_magic_jinja_without_jinja(itikz_magic, capsys, monkeypatch): 197 | monkeypatch.setattr(itikz, 'JINJA2_ENABLED', False) 198 | res = itikz_magic.itikz("--as-jinja", RECTANGLE_TIKZ) 199 | 200 | assert res is None 201 | _, err = capsys.readouterr() 202 | assert err.startswith("Please install jinja2") 203 | 204 | 205 | def test_magic_jinja_print_src(itikz_magic, capsys): 206 | src = "The answer is: {{n}}" 207 | itikz_magic.shell.user_ns['n'] = 42 208 | res = itikz_magic.itikz("--as-jinja --print-jinja", src) 209 | 210 | assert res is None 211 | out, _ = capsys.readouterr() 212 | assert out.startswith("The answer is: 42") 213 | 214 | 215 | def test_implicit_pic(itikz_magic): 216 | src = r"\node[draw] at (0,0) {Hello World};" 217 | res = itikz_magic.itikz("--implicit-pic --temp-dir", src) 218 | assert isinstance(res, SVG) 219 | 220 | 221 | def test_implicit_standalone(itikz_magic): 222 | pic = r"\node[draw] at (0,0) {Hello World};" 223 | src = "\\begin{tikzpicture}\n" + pic + "\n\\end{tikzpicture}\n" 224 | cmd = "--implicit-standalone --tex-packages=tikz --temp-dir" 225 | res = itikz_magic.itikz(cmd, src) 226 | assert isinstance(res, SVG) 227 | 228 | 229 | def test_rasterize_good_input(itikz_magic): 230 | pic = r"\node[draw] at (0,0) {Hello World};" 231 | src = "\\begin{tikzpicture}\n" + pic + "\n\\end{tikzpicture}\n" 232 | cmd = "--implicit-standalone --tex-packages=tikz --temp-dir --rasterize" 233 | res = itikz_magic.itikz(cmd, src) 234 | assert isinstance(res, Image) 235 | 236 | 237 | def test_rasterize_bad_input(itikz_magic): 238 | cmd = "--temp-dir --rasterize" 239 | res = itikz_magic.itikz(cmd, BAD_TIKZ) 240 | assert res is None 241 | 242 | 243 | def test_rasterize_no_cairo_svg(itikz_magic, monkeypatch, capsys): 244 | monkeypatch.setattr(itikz, 'CAIROSVG_ENABLED', False) 245 | 246 | pic = r"\node[draw] at (0,0) {Hello World};" 247 | src = "\\begin{tikzpicture}\n" + pic + "\n\\end{tikzpicture}\n" 248 | cmd = "--implicit-standalone --tex-packages=tikz --temp-dir --rasterize" 249 | res = itikz_magic.itikz(cmd, BAD_TIKZ) 250 | 251 | assert res is None 252 | _, err = capsys.readouterr() 253 | assert err.startswith("Please install cairosvg") 254 | --------------------------------------------------------------------------------