├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── submit-slides-to-gallery.md ├── .gitignore ├── .readthedocs.yml ├── HISTORY.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _static │ ├── codio.gif │ ├── css │ │ └── gallery.css │ ├── demo.gif │ ├── favicon.ico │ ├── gallery │ │ ├── back-to-school.gif │ │ ├── payment-systems-in-india.png │ │ ├── the-hitchhikers-guide-to-clis-in-python.png │ │ ├── trustless-bridges.gif │ │ └── welcome-to-coding.gif │ └── present.png ├── _templates │ ├── hacks.html │ ├── sidebarintro.html │ └── sidebarlogo.html ├── _themes │ ├── .gitignore │ ├── LICENSE │ └── flask_theme_support.py ├── codio.rst ├── conf.py ├── contributing.rst ├── gallery.py ├── gallery │ ├── back-to-school │ │ ├── codios │ │ │ ├── async.yml │ │ │ └── sync.yml │ │ ├── index.rst │ │ └── slides.md │ ├── index.rst │ ├── payment-systems-in-india │ │ ├── images │ │ │ └── paytm.jpg │ │ ├── index.rst │ │ └── slides.md │ ├── the-hitchhikers-guide-to-clis-in-python │ │ ├── codios │ │ │ ├── echo.yml │ │ │ ├── git-clone.yml │ │ │ ├── git-commit.yml │ │ │ ├── git-config.yml │ │ │ ├── git-log.yml │ │ │ ├── git-push.yml │ │ │ ├── git-status.yml │ │ │ ├── icanon.yml │ │ │ ├── onlcr.yml │ │ │ └── progress-bar.yml │ │ ├── images │ │ │ ├── camelot.png │ │ │ ├── pyconline-au.jpg │ │ │ └── recurse-center.png │ │ ├── index.rst │ │ └── slides.md │ ├── trustless-bridges │ │ ├── index.rst │ │ └── slides.md │ └── welcome-to-coding │ │ ├── codios │ │ └── start_class.yml │ │ ├── index.rst │ │ └── slides.md ├── index.rst └── make.bat ├── examples ├── codio.md ├── codio.yml ├── images │ └── recurse.png └── sample.md ├── present.png ├── present ├── __init__.py ├── __main__.py ├── __version__.py ├── cli.py ├── effects.py ├── markdown.py ├── slide.py └── slideshow.py ├── pyproject.toml └── setup.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You can follow this template to submit bug reports. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **Steps to reproduce the bug** 16 | Steps used to install `present`: 17 | 1. Add step here (you can add more steps too) 18 | 19 | Steps to reproduce the behavior: 20 | 1. Add step here (you can add more steps too) 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Slides** 26 | If applicable, add slides to help explain your problem. 27 | ``` 28 | Add markdown for the slide that caused the problem 29 | ``` 30 | 31 | **Screenshots** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | **Environment** 35 | - OS: [e.g. MacOS] 36 | - Terminal emulator: [e.g. guake] 37 | - Python version: [e.g. 3.7] 38 | - `present` version: [e.g. 0.5.1] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/submit-slides-to-gallery.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Submit slides to gallery 3 | about: You can follow this template to submit your slides to the gallery. 4 | title: Add slides to gallery 5 | labels: made-with-present 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Title** 11 | 12 | 13 | **Description** 14 | 15 | 16 | **Image** 17 | 18 | 19 | **Link** 20 | 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | # Build documentation with MkDocs 13 | #mkdocs: 14 | # configuration: mkdocs.yml 15 | 16 | # Optionally build your docs in additional formats such as PDF 17 | formats: 18 | - pdf 19 | 20 | # Optionally set the version of Python and requirements required to build your docs 21 | python: 22 | version: 3.8 23 | install: 24 | - method: pip 25 | path: . 26 | extra_requirements: 27 | - dev 28 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | Release History 2 | =============== 3 | 4 | master 5 | ------ 6 | 7 | 0.6.0 (2020-09-09) 8 | ------------------ 9 | 10 | **Enhancements** 11 | 12 | * [#88](https://github.com/vinayak-mehta/present/issues/88) Support Page Up/Down for switching slides. [#90](https://github.com/vinayak-mehta/present/pull/90) by [Gaby](https://github.com/GitSquared). 13 | 14 | **Bugfixes** 15 | 16 | * [#83](https://github.com/vinayak-mehta/present/issues/83) Having two consecutive image inclusion lines causes NotImplementedError. [#85](https://github.com/vinayak-mehta/present/pull/85) by Vinayak Mehta. 17 | * [#82](https://github.com/vinayak-mehta/present/issues/82) Using markdown comments throws NotImplementedError. [#84](https://github.com/vinayak-mehta/present/pull/84) by Vinayak Mehta. 18 | * [#80](https://github.com/vinayak-mehta/present/issues/80) Image paths aren't relative to the current working directory. [#81](https://github.com/vinayak-mehta/present/pull/81) by [Tymoteusz Makowski](https://github.com/tmakowski). 19 | 20 | **Documentation** 21 | 22 | * Add a gallery! [#74](https://github.com/vinayak-mehta/present/pull/74) by Vinayak Mehta. 23 | * [#36](https://github.com/vinayak-mehta/present/issues/36) Add contributing guide. [#69](https://github.com/vinayak-mehta/present/pull/69) by Vinayak Mehta. 24 | 25 | 0.5.1 (2020-08-31) 26 | ------------------ 27 | 28 | **Bugfixes** 29 | 30 | * [#48](https://github.com/vinayak-mehta/present/issues/48), [#54](https://github.com/vinayak-mehta/present/issues/54), [#52](https://github.com/vinayak-mehta/present/issues/52) Remove italics support temporarily. [#58](https://github.com/vinayak-mehta/present/pull/58) by Vinayak Mehta. 31 | * [#55](https://github.com/vinayak-mehta/present/issues/55) Yank versions older than `0.5.0` from PyPI because they didn't have `python_requires>=3.7`. 32 | 33 | 0.5.0 (2020-08-30) 34 | ------------------ 35 | 36 | **Enhancements** 37 | 38 | * [#26](https://github.com/vinayak-mehta/present/issues/26) Add codio support. [#20](https://github.com/vinayak-mehta/present/pull/20) by Vinayak Mehta. 39 | * [#35](https://github.com/vinayak-mehta/present/issues/35) Support bold/italic text, inline code, links and block quotes. [#38](https://github.com/vinayak-mehta/present/pull/38) by Vinayak Mehta. 40 | * [#32](https://github.com/vinayak-mehta/present/issues/32) Make level 3 headings bold. [#33](https://github.com/vinayak-mehta/present/pull/33) by Vinayak Mehta. 41 | * Allow pressing spacebar to go to next slide. [#30](https://github.com/vinayak-mehta/present/pull/30) by [Thomas Royal](https://github.com/tmroyal). 42 | * [#27](https://github.com/vinayak-mehta/present/issues/27) Don't raise an error for unsupported markdown elements. [#34](https://github.com/vinayak-mehta/present/pull/34) by Vinayak Mehta. 43 | * Unvendor `mistune`. 44 | 45 | **Bugfixes** 46 | 47 | * [#28](https://github.com/vinayak-mehta/present/issues/28) Render single elements using mid point. [#31](https://github.com/vinayak-mehta/present/pull/31) by Vinayak Mehta. 48 | 49 | 0.4.0 (2020-08-27) 50 | ------------------ 51 | 52 | **Enhancements** 53 | 54 | * Allow `Slideshow` to be used as a context manager. [#18](https://github.com/vinayak-mehta/present/pull/18) by [Clint Lawrence](https://github.com/clint-lawrence). 55 | 56 | Also, the earlier duct tape fix `os.system('reset')` (to not leave the terminal in an abnormal state after exit) is replaced with a `screen.close()` which is much better because the earlier fix wouldn't work on Windows. 57 | 58 | * Move an element to the center when there is only one on a slide. [6a0b045](https://github.com/vinayak-mehta/present/commit/6a0b045d0837dc05729d45427c6fae66a1d197ad) by Vinayak Mehta. 59 | 60 | 0.3.0 (2020-08-20) 61 | ------------------ 62 | 63 | **Enhancements** 64 | 65 | * [#17](https://github.com/vinayak-mehta/present/issues/17) Raise informative error when image file does not exist. [564fa72](https://github.com/vinayak-mehta/present/commit/564fa727ec66eda93684dfaa25b7f6f5a4033972) by Vinayak Mehta. 66 | 67 | **Bugfixes** 68 | 69 | * [#16](https://github.com/vinayak-mehta/present/issues/16) Add variable size for h1 headings. [446385d](https://github.com/vinayak-mehta/present/commit/446385d75690bac940e3eeb665b9118f10c8aed4) by Vinayak Mehta. 70 | 71 | 0.2.0 (2020-08-20) 72 | ------------------ 73 | 74 | * First working release! 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # present 6 | 7 | [![Documentation Status](https://readthedocs.org/projects/present/badge/?version=latest)](https://present.readthedocs.io/en/latest/) [![image](https://img.shields.io/pypi/v/present.svg)](https://pypi.org/project/present/) [![image](https://img.shields.io/pypi/pyversions/present.svg)](https://pypi.org/project/present/) [![image](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![Run on Repl.it](https://repl.it/badge/github/vinayak-mehta/present)](https://repl.it/@amasad/terminal-present) 8 | 9 | A terminal-based presentation tool with colors and effects. 10 | 11 |

12 | 13 |

14 | 15 | You can also play a [codio](https://present.readthedocs.io/en/latest/codio.html) (pre-recorded code block) on a slide. 16 | 17 |

18 | 19 |

20 | 21 | `present` is built on [asciimatics](https://github.com/peterbrittain/asciimatics), and it works with `Python>=3.7`. 22 | 23 | Check out the [gallery](https://present.readthedocs.io/en/latest/gallery/index.html) to see what everyone is making with `present`! You can add your slides by simply [opening an issue](https://github.com/vinayak-mehta/present/issues/new?assignees=&labels=made-with-present&template=submit-slides-to-gallery.md&title=Add+slides+to+gallery). 24 | 25 | ## Installation 26 | 27 | You can simply use pip to install `present`: 28 | 29 | ```bash 30 | $ pip install present 31 | ``` 32 | 33 | ## Usage 34 | 35 | ```bash 36 | $ present sample.md 37 | ``` 38 | 39 | Some controls: 40 | 41 | - Quit: `q` 42 | - Previous slide: `b`, Left arrow, Page Up 43 | - Next slide: `n`, Space bar, Right arrow, Page Down 44 | 45 | At the end, you can press `r` to restart the presentation. 46 | 47 | ## Syntax 48 | 49 | Slides follow [Markdown](https://guides.github.com/features/mastering-markdown/) syntax. You can check out the [sample slides](https://github.com/vinayak-mehta/present/blob/master/examples/sample.md) for reference. 50 | 51 | **Note:** Some things aren't supported yet: 52 | - Effects and colors on the same slide. 53 | - Effects and code on the same slide. 54 | 55 | ### Separator 56 | 57 | Each slide can be separated with a `---`. 58 | 59 | ``` 60 | Slide 1 61 | 62 | --- 63 | 64 | Slide 2 65 | ``` 66 | 67 | ### Headers 68 | 69 | Level 1 headings become figlets, level 2 headings get underlined with `-`, and level 3 headings become bold. 70 | 71 | ``` 72 | # Heading 1 73 | 74 | ## Heading 2 75 | 76 | ### Heading 3 77 | ``` 78 | 79 | ### Text 80 | 81 | ``` 82 | This is normal text 83 | 84 | This is **bold text** 85 | 86 | This is `inline code` 87 | 88 | This is a [link](www.google.com) 89 | 90 | As Kanye West said: 91 | 92 | > We're living the future so 93 | > the present is our past. 94 | ``` 95 | 96 | ### Lists 97 | 98 | Ordered lists become unordered lists automatically. 99 | 100 | ``` 101 | - Item 1 102 | - Item 1a 103 | - Item 1b 104 | - Item 1c 105 | - Item 2 106 | - Item 2a 107 | ``` 108 | 109 | ### Images 110 | 111 | Image paths are relative to the directory where your slides are kept, and where you invoke `present`. 112 | 113 | ``` 114 | ![RC](images/recurse.png) 115 | ``` 116 | 117 | Note: You can use high resolution images and tweak the terminal font size to get the best results. 118 | 119 | ### Code blocks 120 | 121 |
122 | ```
123 | import os
124 | 
125 | os.getcwd()
126 | ```
127 | 
128 | 129 | ### Codios 130 | 131 | Codios are pre-recorded playable code blocks which can be useful for live demos. You can find out how to write one in the [codio](https://present.readthedocs.io/en/latest/codio.html) section of the documentation. 132 | 133 | ``` 134 | ![codio](codio.yml) 135 | ``` 136 | 137 | ### Style 138 | 139 | Each slide can be styled with foreground / background colors and effects. By default, slides are black on white with no effects. You can add style to a slide by adding a comment at the beginning of the slide (after the slide separator): 140 | 141 | ``` 142 | Slide 1 143 | 144 | --- 145 | 146 | 147 | Slide 2 148 | 149 | --- 150 | 151 | 152 | Slide 3 153 | ``` 154 | 155 | Colors: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`. 156 | 157 | Effects: `fireworks`, `explosions`, `stars`, `matrix`, `plasma`. More coming soon! 158 | 159 | ## Contributing 160 | 161 | The [Contributor's Guide](https://present.readthedocs.io/en/latest/contributing.html) has detailed information about contributing issues, documentation, code, and tests. 162 | 163 | ## Versioning 164 | 165 | `present` uses [Semantic Versioning](https://semver.org/). For the available versions, see the tags on the GitHub repository. 166 | 167 | ## License 168 | 169 | This project is licensed under the Apache License, see the [LICENSE](https://github.com/vinayak-mehta/present/blob/master/LICENSE) file for details. 170 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/codio.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/codio.gif -------------------------------------------------------------------------------- /docs/_static/css/gallery.css: -------------------------------------------------------------------------------- 1 | div.gallery { 2 | margin: 5px; 3 | border: 1px solid #ccc; 4 | float: left; 5 | width: 180px; 6 | height: 225px; 7 | } 8 | 9 | div.gallery:hover { 10 | border: 1px solid #777; 11 | } 12 | 13 | div.gallery a { 14 | text-decoration: none; 15 | color: inherit; 16 | } 17 | 18 | div.gallery img { 19 | width: 100%; 20 | height: auto; 21 | } 22 | 23 | div.desc { 24 | padding: 10px; 25 | text-align: center; 26 | } 27 | -------------------------------------------------------------------------------- /docs/_static/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/demo.gif -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/gallery/back-to-school.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/gallery/back-to-school.gif -------------------------------------------------------------------------------- /docs/_static/gallery/payment-systems-in-india.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/gallery/payment-systems-in-india.png -------------------------------------------------------------------------------- /docs/_static/gallery/the-hitchhikers-guide-to-clis-in-python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/gallery/the-hitchhikers-guide-to-clis-in-python.png -------------------------------------------------------------------------------- /docs/_static/gallery/trustless-bridges.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/gallery/trustless-bridges.gif -------------------------------------------------------------------------------- /docs/_static/gallery/welcome-to-coding.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/gallery/welcome-to-coding.gif -------------------------------------------------------------------------------- /docs/_static/present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/_static/present.png -------------------------------------------------------------------------------- /docs/_templates/hacks.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 | 6 |

7 | 9 |

10 | 11 |

Useful Links

12 | 18 | -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 6 |

7 | 9 |

-------------------------------------------------------------------------------- /docs/_themes/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the theme, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | We kindly ask you to only use these themes in an unmodified manner just 22 | for Flask and Flask-related products, not for unrelated projects. If you 23 | like the visual style and want to use it for your own projects, please 24 | consider making some larger changes to the themes (such as changing 25 | font faces, sizes, colors or margins). 26 | 27 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import ( 4 | Keyword, 5 | Name, 6 | Comment, 7 | String, 8 | Error, 9 | Number, 10 | Operator, 11 | Generic, 12 | Whitespace, 13 | Punctuation, 14 | Other, 15 | Literal, 16 | ) 17 | 18 | 19 | class FlaskyStyle(Style): 20 | background_color = "#f8f8f8" 21 | default_style = "" 22 | 23 | styles = { 24 | # No corresponding class for the following: 25 | # Text: "", # class: '' 26 | Whitespace: "underline #f8f8f8", # class: 'w' 27 | Error: "#a40000 border:#ef2929", # class: 'err' 28 | Other: "#000000", # class 'x' 29 | Comment: "italic #8f5902", # class: 'c' 30 | Comment.Preproc: "noitalic", # class: 'cp' 31 | Keyword: "bold #004461", # class: 'k' 32 | Keyword.Constant: "bold #004461", # class: 'kc' 33 | Keyword.Declaration: "bold #004461", # class: 'kd' 34 | Keyword.Namespace: "bold #004461", # class: 'kn' 35 | Keyword.Pseudo: "bold #004461", # class: 'kp' 36 | Keyword.Reserved: "bold #004461", # class: 'kr' 37 | Keyword.Type: "bold #004461", # class: 'kt' 38 | Operator: "#582800", # class: 'o' 39 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 40 | Punctuation: "bold #000000", # class: 'p' 41 | # because special names such as Name.Class, Name.Function, etc. 42 | # are not recognized as such later in the parsing, we choose them 43 | # to look the same as ordinary variables. 44 | Name: "#000000", # class: 'n' 45 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 46 | Name.Builtin: "#004461", # class: 'nb' 47 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 48 | Name.Class: "#000000", # class: 'nc' - to be revised 49 | Name.Constant: "#000000", # class: 'no' - to be revised 50 | Name.Decorator: "#888", # class: 'nd' - to be revised 51 | Name.Entity: "#ce5c00", # class: 'ni' 52 | Name.Exception: "bold #cc0000", # class: 'ne' 53 | Name.Function: "#000000", # class: 'nf' 54 | Name.Property: "#000000", # class: 'py' 55 | Name.Label: "#f57900", # class: 'nl' 56 | Name.Namespace: "#000000", # class: 'nn' - to be revised 57 | Name.Other: "#000000", # class: 'nx' 58 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 59 | Name.Variable: "#000000", # class: 'nv' - to be revised 60 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 61 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 62 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 63 | Number: "#990000", # class: 'm' 64 | Literal: "#000000", # class: 'l' 65 | Literal.Date: "#000000", # class: 'ld' 66 | String: "#4e9a06", # class: 's' 67 | String.Backtick: "#4e9a06", # class: 'sb' 68 | String.Char: "#4e9a06", # class: 'sc' 69 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 70 | String.Double: "#4e9a06", # class: 's2' 71 | String.Escape: "#4e9a06", # class: 'se' 72 | String.Heredoc: "#4e9a06", # class: 'sh' 73 | String.Interpol: "#4e9a06", # class: 'si' 74 | String.Other: "#4e9a06", # class: 'sx' 75 | String.Regex: "#4e9a06", # class: 'sr' 76 | String.Single: "#4e9a06", # class: 's1' 77 | String.Symbol: "#4e9a06", # class: 'ss' 78 | Generic: "#000000", # class: 'g' 79 | Generic.Deleted: "#a40000", # class: 'gd' 80 | Generic.Emph: "italic #000000", # class: 'ge' 81 | Generic.Error: "#ef2929", # class: 'gr' 82 | Generic.Heading: "bold #000080", # class: 'gh' 83 | Generic.Inserted: "#00A000", # class: 'gi' 84 | Generic.Output: "#888", # class: 'go' 85 | Generic.Prompt: "#745334", # class: 'gp' 86 | Generic.Strong: "bold #000000", # class: 'gs' 87 | Generic.Subheading: "bold #800080", # class: 'gu' 88 | Generic.Traceback: "bold #a40000", # class: 'gt' 89 | } 90 | -------------------------------------------------------------------------------- /docs/codio.rst: -------------------------------------------------------------------------------- 1 | .. _codio: 2 | 3 | codio 4 | ===== 5 | 6 | A codio is a pre-recorded playable code block which can be useful for live demos. During the presentation, and on the slide with a codio, you can press ``r`` to reset the codio so that it starts playing again from the first line. 7 | 8 | .. image:: _static/codio.gif 9 | 10 | You can make a codio by writing all the input and output as plaintext in a `YAML `_ file (shown below), and use it inside your Markdown slides just like an image: ``![codio](codio.yml)``. The image alt should be ``![codio]``, but filename can be anything. 11 | 12 | .. code-block:: yaml 13 | 14 | speed: 10 15 | lines: 16 | - prompt: $ 17 | in: touch a.txt 18 | - prompt: $ 19 | in: smol-git status 20 | - out: 'On branch master' 21 | - out: 'Changes to be committed:' 22 | - out: ' (use "git reset HEAD ..." to unstage)' 23 | - out: ' ' 24 | - out: ' new file: a.txt' 25 | color: green 26 | bold: True 27 | - out: ' ' 28 | - prompt: $ 29 | in: smol-git add a.txt 30 | - prompt: $ 31 | in: 'smol-git commit -m "Add a.txt"' 32 | - out: '[master b0faa5a] Save progress' 33 | - prompt: $ 34 | in: smol-git push origin master 35 | out: "Pushing to 'origin'..." 36 | - progress: true 37 | progressChar: █ 38 | - prompt: $ 39 | 40 | Let's deconstruct this YAML. 41 | 42 | You can set the speed for your codio by specifying its value in a top-level key called ``speed``. It can be between 1 (very slow) to 10 (very fast). 43 | 44 | .. code-block:: yaml 45 | 46 | speed: 10 47 | 48 | You can specify each line in your code block as a prompt, input, and output item in the ``lines`` list. Input gets printed one character at a time, and output all at once. 49 | 50 | .. code-block:: yaml 51 | 52 | lines: 53 | - prompt: '>>>' 54 | in: 'os.getcwd()' 55 | out: '/home/vinayak/dev' 56 | 57 | You can also choose to skip output for some lines. 58 | 59 | .. code-block:: yaml 60 | 61 | lines: 62 | - prompt: '>>>' 63 | in: 'import os' 64 | 65 | To show a multi-line output (like in the first example), you can just specify one output per line. 66 | 67 | .. code-block:: yaml 68 | 69 | lines: 70 | - out: 'On branch master' 71 | - out: 'Changes to be committed:' 72 | - out: ' (use "git reset HEAD ..." to unstage)' 73 | - out: ' ' 74 | - out: ' new file: a.txt' 75 | 76 | Notice the ``out: ' '`` to print an empty line. 77 | 78 | You can add colors and styles to your output like this: 79 | 80 | .. code-block:: yaml 81 | 82 | lines: 83 | - out: ' new file: a.txt' 84 | color: green 85 | bold: true 86 | 87 | Currently, these colors are supported: ``black``, ``red``, ``green``, ``yellow``, ``blue``, ``magenta``, ``cyan``, ``white``. And these styles are supported: ``bold`` and ``underline``. (``italics`` coming soon!) 88 | 89 | You can add progress bars too. To add one, just set ``progress`` to ``true`` and add a progress character for your progress bar using ``progressChar``. The default ``progressChar`` is ``█``. 90 | 91 | .. code-block:: yaml 92 | 93 | lines: 94 | - progress: true 95 | progressChar: # 96 | 97 | In the end, you can also print just a prompt again! 98 | 99 | .. code-block:: yaml 100 | 101 | lines: 102 | - prompt: $ 103 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # present documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jul 19 13:44:18 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import sys 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | # 22 | # sys.path.insert(0, os.path.abspath('..')) 23 | 24 | # Insert present's path into the system. 25 | sys.path.insert(0, os.path.abspath(".")) 26 | sys.path.insert(0, os.path.abspath("..")) 27 | sys.path.insert(0, os.path.abspath("_themes")) 28 | 29 | import present 30 | 31 | 32 | # -- General configuration ------------------------------------------------ 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = ["gallery"] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ["_templates"] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # 49 | # source_suffix = ['.rst', '.md'] 50 | source_suffix = ".rst" 51 | 52 | # The encoding of source files. 53 | # 54 | # source_encoding = 'utf-8-sig' 55 | 56 | # The master toctree document. 57 | master_doc = "index" 58 | 59 | # General information about the project. 60 | project = u"conference-radar" 61 | copyright = u"2020, Vinayak Mehta" 62 | author = u"Vinayak Mehta" 63 | 64 | # The version info for the project you're documenting, acts as replacement for 65 | # |version| and |release|, also used in various other places throughout the 66 | # built documents. 67 | 68 | # The short X.Y version. 69 | version = present.__version__ 70 | # The full version, including alpha/beta/rc tags. 71 | release = present.__version__ 72 | 73 | # The language for content autogenerated by Sphinx. Refer to documentation 74 | # for a list of supported languages. 75 | # 76 | # This is also used if you do content translation via gettext catalogs. 77 | # Usually you set "language" from the command line for these cases. 78 | language = None 79 | 80 | # There are two options for replacing |today|: either, you set today to some 81 | # non-false value, then it is used: 82 | # 83 | # today = '' 84 | # 85 | # Else, today_fmt is used as the format for a strftime call. 86 | # 87 | # today_fmt = '%B %d, %Y' 88 | 89 | # List of patterns, relative to source directory, that match files and 90 | # directories to ignore when looking for source files. 91 | # This patterns also effect to html_static_path and html_extra_path 92 | exclude_patterns = ["_build"] 93 | 94 | # The reST default role (used for this markup: `text`) to use for all 95 | # documents. 96 | # 97 | # default_role = None 98 | 99 | # If true, '()' will be appended to :func: etc. cross-reference text. 100 | add_function_parentheses = True 101 | 102 | # If true, the current module name will be prepended to all description 103 | # unit titles (such as .. function::). 104 | add_module_names = True 105 | 106 | # If true, sectionauthor and moduleauthor directives will be shown in the 107 | # output. They are ignored by default. 108 | # 109 | # show_authors = False 110 | 111 | # The name of the Pygments (syntax highlighting) style to use. 112 | pygments_style = "flask_theme_support.FlaskyStyle" 113 | 114 | # A list of ignored prefixes for module index sorting. 115 | # modindex_common_prefix = [] 116 | 117 | # If true, keep warnings as "system message" paragraphs in the built documents. 118 | # keep_warnings = False 119 | 120 | # If true, `todo` and `todoList` produce output, else they produce nothing. 121 | todo_include_todos = True 122 | 123 | 124 | # -- Options for HTML output ---------------------------------------------- 125 | 126 | # The theme to use for HTML and HTML Help pages. See the documentation for 127 | # a list of builtin themes. 128 | html_theme = "alabaster" 129 | 130 | # Theme options are theme-specific and customize the look and feel of a theme 131 | # further. For a list of options available for each theme, see the 132 | # documentation. 133 | html_theme_options = { 134 | "show_powered_by": False, 135 | "github_user": "vinayak-mehta", 136 | "github_repo": "present", 137 | "github_banner": True, 138 | "show_related": False, 139 | "note_bg": "#FFF59C", 140 | } 141 | 142 | # Add any paths that contain custom themes here, relative to this directory. 143 | # html_theme_path = [] 144 | 145 | # The name for this set of Sphinx documents. 146 | # " v documentation" by default. 147 | # 148 | # html_title = None 149 | 150 | # A shorter title for the navigation bar. Default is the same as html_title. 151 | # 152 | # html_short_title = None 153 | 154 | # The name of an image file (relative to this directory) to place at the top 155 | # of the sidebar. 156 | # 157 | # html_logo = None 158 | 159 | # The name of an image file (relative to this directory) to use as a favicon of 160 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 161 | # pixels large. 162 | html_favicon = "_static/favicon.ico" 163 | 164 | # Add any paths that contain custom static files (such as style sheets) here, 165 | # relative to this directory. They are copied after the builtin static files, 166 | # so a file named "default.css" will overwrite the builtin "default.css". 167 | html_static_path = ["_static"] 168 | 169 | # These paths are either relative to html_static_path 170 | # or fully qualified paths (eg. https://...) 171 | html_css_files = [ 172 | "css/gallery.css", 173 | ] 174 | 175 | # Add any extra paths that contain custom files (such as robots.txt or 176 | # .htaccess) here, relative to this directory. These files are copied 177 | # directly to the root of the documentation. 178 | # 179 | # html_extra_path = [] 180 | 181 | # If not None, a 'Last updated on:' timestamp is inserted at every page 182 | # bottom, using the given strftime format. 183 | # The empty string is equivalent to '%b %d, %Y'. 184 | # 185 | # html_last_updated_fmt = None 186 | 187 | # If true, SmartyPants will be used to convert quotes and dashes to 188 | # typographically correct entities. 189 | html_use_smartypants = True 190 | 191 | # Custom sidebar templates, maps document names to template names. 192 | html_sidebars = { 193 | "index": [ 194 | "sidebarintro.html", 195 | "relations.html", 196 | "sourcelink.html", 197 | "searchbox.html", 198 | "hacks.html", 199 | ], 200 | "**": [ 201 | "sidebarlogo.html", 202 | "localtoc.html", 203 | "relations.html", 204 | "sourcelink.html", 205 | "searchbox.html", 206 | "hacks.html", 207 | ], 208 | } 209 | 210 | # Additional templates that should be rendered to pages, maps page names to 211 | # template names. 212 | # 213 | # html_additional_pages = {} 214 | 215 | # If false, no module index is generated. 216 | # 217 | # html_domain_indices = True 218 | 219 | # If false, no index is generated. 220 | # 221 | # html_use_index = True 222 | 223 | # If true, the index is split into individual pages for each letter. 224 | # 225 | # html_split_index = False 226 | 227 | # If true, links to the reST sources are added to the pages. 228 | html_show_sourcelink = False 229 | 230 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 231 | html_show_sphinx = False 232 | 233 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 234 | html_show_copyright = True 235 | 236 | # If true, an OpenSearch description file will be output, and all pages will 237 | # contain a tag referring to it. The value of this option must be the 238 | # base URL from which the finished HTML is served. 239 | # 240 | # html_use_opensearch = '' 241 | 242 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 243 | # html_file_suffix = None 244 | 245 | # Language to be used for generating the HTML full-text search index. 246 | # Sphinx supports the following languages: 247 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 248 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 249 | # 250 | # html_search_language = 'en' 251 | 252 | # A dictionary with options for the search language support, empty by default. 253 | # 'ja' uses this config value. 254 | # 'zh' user can custom change `jieba` dictionary path. 255 | # 256 | # html_search_options = {'type': 'default'} 257 | 258 | # The name of a javascript file (relative to the configuration directory) that 259 | # implements a search results scorer. If empty, the default will be used. 260 | # 261 | # html_search_scorer = 'scorer.js' 262 | 263 | # Output file base name for HTML help builder. 264 | htmlhelp_basename = "presentdoc" 265 | 266 | # -- Options for LaTeX output --------------------------------------------- 267 | 268 | latex_elements = { 269 | # The paper size ('letterpaper' or 'a4paper'). 270 | # 271 | # 'papersize': 'letterpaper', 272 | # The font size ('10pt', '11pt' or '12pt'). 273 | # 274 | # 'pointsize': '10pt', 275 | # Additional stuff for the LaTeX preamble. 276 | # 277 | # 'preamble': '', 278 | # Latex figure (float) alignment 279 | # 280 | # 'figure_align': 'htbp', 281 | } 282 | 283 | # Grouping the document tree into LaTeX files. List of tuples 284 | # (source start file, target name, title, 285 | # author, documentclass [howto, manual, or own class]). 286 | latex_documents = [ 287 | ( 288 | master_doc, 289 | "present.tex", 290 | u"present documentation", 291 | u"Vinayak Mehta", 292 | "manual", 293 | ) 294 | ] 295 | 296 | # The name of an image file (relative to this directory) to place at the top of 297 | # the title page. 298 | # 299 | # latex_logo = None 300 | 301 | # For "manual" documents, if this is true, then toplevel headings are parts, 302 | # not chapters. 303 | # 304 | # latex_use_parts = False 305 | 306 | # If true, show page references after internal links. 307 | # 308 | # latex_show_pagerefs = False 309 | 310 | # If true, show URL addresses after external links. 311 | # 312 | # latex_show_urls = False 313 | 314 | # Documents to append as an appendix to all manuals. 315 | # 316 | # latex_appendices = [] 317 | 318 | # It false, will not define \strong, \code, itleref, \crossref ... but only 319 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 320 | # packages. 321 | # 322 | # latex_keep_old_macro_names = True 323 | 324 | # If false, no module index is generated. 325 | # 326 | # latex_domain_indices = True 327 | 328 | 329 | # -- Options for manual page output --------------------------------------- 330 | 331 | # One entry per manual page. List of tuples 332 | # (source start file, name, description, authors, manual section). 333 | man_pages = [(master_doc, "present", u"present documentation", [author], 1)] 334 | 335 | # If true, show URL addresses after external links. 336 | # 337 | # man_show_urls = False 338 | 339 | 340 | # -- Options for Texinfo output ------------------------------------------- 341 | 342 | # Grouping the document tree into Texinfo files. List of tuples 343 | # (source start file, target name, title, author, 344 | # dir menu entry, description, category) 345 | texinfo_documents = [ 346 | ( 347 | master_doc, 348 | "present", 349 | u"present documentation", 350 | author, 351 | "present", 352 | "One line description of project.", 353 | "Miscellaneous", 354 | ) 355 | ] 356 | 357 | # Documents to append as an appendix to all manuals. 358 | # 359 | # texinfo_appendices = [] 360 | 361 | # If false, no module index is generated. 362 | # 363 | # texinfo_domain_indices = True 364 | 365 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 366 | # 367 | # texinfo_show_urls = 'footnote' 368 | 369 | # If true, do not generate a @detailmenu in the "Top" node's menu. 370 | # 371 | # texinfo_no_detailmenu = False 372 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | Contributor's Guide 4 | =================== 5 | 6 | Thanks for taking the time to contribute! 7 | 8 | This doc will help you get started with contributing issues, documentation, code, and tests. If you have any questions, feel free to reach out to `Vinayak Mehta`_, the author and maintainer. 9 | 10 | .. _Vinayak Mehta: https://github.com/vinayak-mehta 11 | 12 | Filing Issues 13 | ------------- 14 | 15 | ``present`` uses `GitHub issues`_ to keep track of all issues and pull requests. Before opening an issue with a question or a bug report, please use the issue search feature to look for existing issues (both open and closed) that may be similar. 16 | 17 | .. _GitHub issues: https://github.com/vinayak-mehta/present/issues 18 | 19 | When opening an issue: 20 | 21 | 1. List the steps you used to install ``present``. 22 | 23 | 2. Make sure you include your operating system name, terminal emulator name, Python version number, and ``present`` version number. You can use the following code snippet to find most of this information:: 24 | 25 | import platform; print('Platform', platform.platform()) 26 | import sys; print('Python', sys.version) 27 | import present; print('Present', present.__version__) 28 | 29 | 3. Make sure you provide a suitable amount of information to work with. For example, the Markdown for the slide causing the issue in a `code block`_ (you can replace sensitive text with `lorem ipsum`_), what you expected to happen, and what actually happened. 30 | 31 | .. _lorem ipsum: https://www.lipsum.com/ 32 | 33 | 4. When filing bug reports about exceptions or tracebacks, please include the complete traceback in a `code block`_, along with everything from 2. 34 | 35 | 5. When suggesting enhancements, please use a clear and descriptive title, along with a very detailed description of the suggested enhancement. 36 | 37 | .. _code block: https://help.github.com/articles/creating-and-highlighting-code-blocks/ 38 | 39 | Contributing Docs and Code 40 | -------------------------- 41 | 42 | Setting up the development environment 43 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 44 | 45 | To install the dependencies needed for development, you can use pip:: 46 | 47 | $ pip install "present[dev]" 48 | 49 | Alternatively, you can clone the project repository, and use pip again to install everything:: 50 | 51 | $ cd present 52 | $ pip install ".[dev]" 53 | 54 | Writing Documentation 55 | ^^^^^^^^^^^^^^^^^^^^^ 56 | 57 | Writing documentation, function docstrings, examples and tutorials is a great way to start contributing to free and open-source software! The documentation for this project lies in the ``docs/`` directory of the repository, and it is written in `reStructuredText`_ with `Sphinx`_ used to generate these lovely HTML files that you're currently reading (unless you're reading this on GitHub). You can edit the documentation using any text editor and then submit a pull request. 58 | 59 | .. _reStructuredText: https://en.wikipedia.org/wiki/ReStructuredText 60 | .. _Sphinx: http://www.sphinx-doc.org/en/master/ 61 | 62 | Contributing Code 63 | ^^^^^^^^^^^^^^^^^ 64 | 65 | Another great way to start contributing to ``present`` is to pick an issue tagged with the `help wanted`_ or the `good first issue`_ tags. If you're unable to find a good first issue, feel free to contact the maintainer. 66 | 67 | .. _help wanted: https://github.com/vinayak-mehta/present/labels/help%20wanted 68 | .. _good first issue: https://github.com/vinayak-mehta/present/labels/good%20first%20issue 69 | 70 | Pull Requests 71 | ------------- 72 | 73 | Submitting your pull request 74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 75 | 76 | The preferred workflow for contributing to ``present`` is to fork the `project repository`_ on GitHub, clone, develop on a branch and then finally submit a pull request. Here are the steps: 77 | 78 | .. _project repository: https://github.com/vinayak-mehta/present 79 | 80 | 1. Fork the project repository. Click on the ‘Fork’ button near the top of the page. This creates a copy of the code under your account on the GitHub. 81 | 82 | 2. Clone your fork of ``present`` from your GitHub account:: 83 | 84 | $ git clone https://www.github.com/[username]/present 85 | 86 | 3. Create a branch to hold your changes:: 87 | 88 | $ git checkout -b my-feature 89 | 90 | Always branch out from ``master`` to work on your contribution. It's good practice to never work on the ``master`` branch! 91 | 92 | .. note:: ``git stash`` is a great way to save the work that you haven't committed yet, to move between branches. 93 | 94 | 4. Work on your contribution. Add changed files using ``git add`` and then ``git commit`` them:: 95 | 96 | $ git add modified_files 97 | $ git commit 98 | 99 | 5. Finally, push them to your GitHub fork:: 100 | 101 | $ git push -u origin my-feature 102 | 103 | Now it's time to go to the your fork of ``present`` and create a pull request! You can `follow these instructions`_ to do that. 104 | 105 | .. _follow these instructions: https://help.github.com/articles/creating-a-pull-request-from-a-fork/ 106 | 107 | Working on your pull request 108 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 109 | 110 | It's recommended that your pull request complies with the following guidelines: 111 | 112 | - Make sure your code follows `pep8`_. You can also run `black`_ on your code since ``present`` follows ``black`` code style. 113 | 114 | .. _pep8: http://pep8.org 115 | .. _black: https://black.readthedocs.io/en/stable/ 116 | 117 | - In case your pull request contains function docstrings, make sure you follow the `numpydoc`_ format. 118 | 119 | .. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html 120 | 121 | - Make sure your commit messages follow `the seven rules of a great git commit message`_: 122 | - Separate subject from body with a blank line 123 | - Limit the subject line to 50 characters 124 | - Capitalize the subject line 125 | - Do not end the subject line with a period 126 | - Use the imperative mood in the subject line 127 | - Wrap the body at 72 characters 128 | - Use the body to explain what and why vs. how 129 | 130 | .. _the seven rules of a great git commit message: https://chris.beams.io/posts/git-commit/ 131 | 132 | - If the contribution is complete and ready for a detailed review, prefix your title of your pull request with ``[MRG]`` (Ready for Merge). An incomplete pull request's title should be prefixed with ``[WIP]`` (to indicate work in progress), and changed to ``[MRG]`` when it's complete. A good `task list`_ in the PR description will ensure that other people get a fair idea of what it proposes to do, which will also increase collaboration. 133 | 134 | .. _task list: https://blog.github.com/2013-01-09-task-lists-in-gfm-issues-pulls-comments/ 135 | -------------------------------------------------------------------------------- /docs/gallery.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from docutils.nodes import Body, Element, SkipNode 4 | from docutils.parsers.rst import Directive, directives 5 | 6 | 7 | class Node(Body, Element): 8 | pass 9 | 10 | 11 | class GalleryImage(Directive): 12 | has_content = False 13 | required_arguments = 0 14 | optional_arguments = 0 15 | final_argument_whitespace = True 16 | option_spec = { 17 | "src": directives.unchanged, 18 | "stub": directives.unchanged, 19 | "description": directives.unchanged, 20 | } 21 | 22 | def run(self): 23 | node = Node() 24 | node["src"] = self.options["src"] 25 | node["stub"] = self.options["stub"] 26 | node["description"] = self.options["description"] 27 | return [node] 28 | 29 | 30 | def gallery_image_html(self, node): 31 | src = node["src"] 32 | stub = node["stub"] 33 | description = node["description"] 34 | 35 | template = f""" 36 | 42 | """ 43 | self.body.append(template) 44 | 45 | raise SkipNode 46 | 47 | 48 | def setup(app): 49 | app.add_node(Node, html=(gallery_image_html, None)) 50 | app.add_directive("gallery_image", GalleryImage) 51 | -------------------------------------------------------------------------------- /docs/gallery/back-to-school/codios/async.yml: -------------------------------------------------------------------------------- 1 | speed: 8 2 | lines: 3 | - prompt: $ 4 | in: project creation using 5 | bold: True 6 | - out: ' ' 7 | - out: ' - edhesive' 8 | - out: ' - trinket.io' 9 | - out: ' - HTML?' 10 | -------------------------------------------------------------------------------- /docs/gallery/back-to-school/codios/sync.yml: -------------------------------------------------------------------------------- 1 | speed: 8 2 | lines: 3 | - prompt: $ 4 | in: How will we meet? 5 | bold: True 6 | - out: ' ' 7 | - out: ' -Zoom Meetings' 8 | - out: ' ' 9 | - prompt: $ 10 | in: What will we do? 11 | bold: True 12 | - out: ' ' 13 | - out: ' - breakout room debugging' 14 | - out: ' - sharing awesome projects' 15 | - out: ' - trinket.io' 16 | -------------------------------------------------------------------------------- /docs/gallery/back-to-school/index.rst: -------------------------------------------------------------------------------- 1 | Gallery 2 | ======= 3 | 4 | Back to School 5 | -------------- 6 | 7 | I made a presentation for back to school night for virtual learning. 8 | 9 | .. image:: /_static/gallery/back-to-school.gif 10 | :align: center 11 | 12 | Made by `Russell Helmstedter `_. 13 | 14 | You can check out all files `here`_. 15 | 16 | .. _here: https://github.com/vinayak-mehta/present/tree/master/docs/gallery/back-to-school 17 | 18 | Markdown slides: 19 | 20 | .. literalinclude:: slides.md 21 | -------------------------------------------------------------------------------- /docs/gallery/back-to-school/slides.md: -------------------------------------------------------------------------------- 1 | # Welcome 2 | 3 | --- 4 | 5 | ## Distance Learning 6 | 7 | - Synchronous 8 | - Asynchronous 9 | 10 | --- 11 | 12 | ## Synchronous 13 | 14 | ![codio](codios/sync.yml) 15 | 16 | --- 17 | 18 | ## Asynchronous 19 | 20 | ![codio](codios/async.yml) 21 | 22 | --- 23 | 24 | ## Grades 25 | 26 | Canvas 27 | 28 | communication --> flexibility 29 | 30 | --- 31 | 32 | # Contact 33 | 34 | ## my_email@domain.org 35 | 36 | --- 37 | 38 | -------------------------------------------------------------------------------- /docs/gallery/index.rst: -------------------------------------------------------------------------------- 1 | .. _gallery: 2 | 3 | Gallery 4 | ======= 5 | 6 | You can add your slides by simply `opening an issue`_! 7 | 8 | .. _opening an issue: https://github.com/vinayak-mehta/present/issues/new?assignees=&labels=made-with-present&template=submit-slides-to-gallery.md&title=Add+slides+to+gallery 9 | 10 | .. gallery_image:: 11 | :src: _static/gallery/trustless-bridges.gif 12 | :stub: trustless-bridges 13 | :description: Trustless Bridges by Hernando Castano 14 | 15 | .. gallery_image:: 16 | :src: _static/gallery/payment-systems-in-india.png 17 | :stub: payment-systems-in-india 18 | :description: Payment Systems in India by Nemo 19 | 20 | .. gallery_image:: 21 | :src: _static/gallery/back-to-school.gif 22 | :stub: back-to-school 23 | :description: Back to School by Russell Helmstedter 24 | 25 | .. gallery_image:: 26 | :src: _static/gallery/welcome-to-coding.gif 27 | :stub: welcome-to-coding 28 | :description: Welcome to Coding by Russell Helmstedter 29 | 30 | .. gallery_image:: 31 | :src: _static/gallery/the-hitchhikers-guide-to-clis-in-python.png 32 | :stub: the-hitchhikers-guide-to-clis-in-python 33 | :description: The Hitchhiker's Guide to CLIs in Python by Vinayak Mehta 34 | -------------------------------------------------------------------------------- /docs/gallery/payment-systems-in-india/images/paytm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/gallery/payment-systems-in-india/images/paytm.jpg -------------------------------------------------------------------------------- /docs/gallery/payment-systems-in-india/index.rst: -------------------------------------------------------------------------------- 1 | Gallery 2 | ======= 3 | 4 | Payment Systems in India 5 | ------------------------ 6 | 7 | A talk about how financial payments in India work. 8 | 9 | .. image:: /_static/gallery/payment-systems-in-india.png 10 | :align: center 11 | 12 | Made by `Nemo `_. 13 | 14 | You can check out all files `here`_. 15 | 16 | .. _here: https://github.com/vinayak-mehta/present/tree/master/docs/gallery/payment-systems-in-india 17 | 18 | Markdown slides: 19 | 20 | .. literalinclude:: slides.md 21 | -------------------------------------------------------------------------------- /docs/gallery/payment-systems-in-india/slides.md: -------------------------------------------------------------------------------- 1 | ## payment systems in india 2 | 3 | --- 4 | 5 | Ref: https://events.ccc.de/camp/2019/wiki/Session:Payment_Systems_in_India_(Rescheduled) 6 | 7 | --- 8 | 9 | # rbi 10 | 11 | reserve bank of india 12 | 13 | --- 14 | 15 | # banks 16 | 17 | ## Public 18 | - State Bank of India ($590B) 19 | - Punjab National Bank ($120B) 20 | 21 | ## Private 22 | - HDFC ($220B) 23 | - ICICI ($140B) 24 | 25 | Total More than 1500 banks. 26 | 27 | Public=Govt is majority owner 28 | 29 | --- 30 | 31 | # npci 32 | 33 | natonal payments corporation of india 34 | 35 | - umbrella organization 36 | - owned by banks 37 | - authorized by rbi 38 | - operates retail payments in india 39 | - not-for-profit 40 | 41 | (not a government entity) 42 | 43 | --- 44 | 45 | 46 | 47 | ## all the ways you can pay 48 | 49 | ### digitally 50 | 51 | --- 52 | 53 | # numbers 54 | 55 | - adult population (800M) 56 | - bank accounts (1.6B) 57 | - debit card (660M) 58 | - credit card (30M) 59 | 60 | (might be a few years old) 61 | 62 | --- 63 | 64 | ## card payments 65 | 66 | - 3D Secure (2FA mandate) 67 | - RuPay Card network (run by NPCI) 68 | - 4M PoS devices 69 | 70 | (PoS = Point of Sale) 71 | 72 | --- 73 | 74 | 79 | 80 | ## NEFT/RTGS/IMPS 81 | 82 | Bank-Bank transfer 83 | 84 | - RTGS: National Electronic Fund Transfer (run by RBI) 85 | - RTGS: Real Time Gross Settlement (run by RBI) 86 | - NEFT runs at 30 minute batches 24x7x365 87 | - IMPS is instant 88 | 89 | --- 90 | 91 | # historically: 92 | 93 | - NEFT would run once every business hour 94 | - RTGS would be real-time but only support high-value transactions to keep loads bearable 95 | 96 | 97 | --- 98 | 99 | # wallets 100 | 101 | ![](images/paytm.jpg) 102 | 103 | --- 104 | 105 | # pre-payment-instrument 106 | 107 | - Gift Cards 108 | - Meal vouchers 109 | 110 | (Things that are currency, but not really currency) 111 | 112 | --- 113 | 114 | # wallets = ppi 115 | 116 | bank account -> wallet 117 | 118 | your wallet = ledger 119 | 120 | - transactions 121 | - p2p transfers 122 | 123 | --- 124 | 125 | # sachet economy 126 | 127 | - wallet bloom 128 | - no 2FA! 129 | 130 | --- 131 | 132 | # NOV 09 2016 133 | 134 | --- 135 | 136 | # UPI 137 | 138 | Unified Payments Interface 139 | 140 | --- 141 | 142 | - mobile-first 143 | - shared-banking-platform 144 | - works-across-banks 145 | - run by npci 146 | - 25+ apps on both Android/iOS 147 | 148 | --- 149 | 150 | 1. authenticate with your phone number 151 | 2. pick a bank account 152 | 3. transact with your PIN for your bank account 153 | 154 | --- 155 | 156 | ## current players: 157 | 158 | - PhonePe (owned by Flipkart->Walmart) (20-30%) 159 | - WhatsApp (FB) (in beta) 160 | - GPay (30-60%) 161 | - Paytm (5%) 162 | 163 | --- 164 | 165 | # interested in more? 166 | 167 | - Tuesday 1st Sep 168 | - 1730 UTC / 1300 EST / 2230 IST 169 | - (spoilers) 170 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/echo.yml: -------------------------------------------------------------------------------- 1 | speed: 5 2 | lines: 3 | - prompt: $ 4 | in: cat 5 | out: '' 6 | - prompt: '' 7 | in: hello 8 | out: hello 9 | - prompt: '' 10 | in: world 11 | out: world 12 | - prompt: '' 13 | in: '' 14 | out: ^C 15 | - prompt: $ 16 | in: stty -echo 17 | out: '' 18 | - prompt: '' 19 | in: ' ' 20 | out: '' 21 | - prompt: '' 22 | in: '' 23 | out: hello 24 | - prompt: '' 25 | in: ' ' 26 | out: '' 27 | - prompt: '' 28 | in: '' 29 | out: world 30 | - prompt: $ 31 | in: ' ' 32 | out: '' 33 | - prompt: $ 34 | in: cat 35 | out: '' 36 | - prompt: '' 37 | in: hello 38 | out: hello 39 | - prompt: '' 40 | in: world 41 | out: world 42 | - prompt: '' 43 | in: $ 44 | out: '' 45 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/git-clone.yml: -------------------------------------------------------------------------------- 1 | speed: 2 2 | lines: 3 | - prompt: $ 4 | in: smol-git clone git@github.com:vinayak-mehta/smol-git 5 | out: '' 6 | - prompt: '' 7 | in: ████████████████████████ 8 | out: '' 9 | - prompt: '' 10 | in: $ 11 | out: '' 12 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/git-commit.yml: -------------------------------------------------------------------------------- 1 | speed: 4 2 | lines: 3 | - prompt: $ 4 | in: smol-git commit 5 | out: '' 6 | - prompt: '' 7 | in: '' 8 | out: '# Please enter the commit message for your changes. Lines starting' 9 | - prompt: '' 10 | in: '' 11 | out: "# with '#' will be ignored, and an empty message aborts the commit." 12 | - prompt: '' 13 | in: '' 14 | out: "# On branch master" 15 | - prompt: '' 16 | in: '' 17 | out: "# Changes to be committed:" 18 | - prompt: '' 19 | in: '' 20 | out: "# new file: a.txt" 21 | - prompt: '' 22 | in: '' 23 | out: '#' 24 | - prompt: '' 25 | in: ' ' 26 | out: '' 27 | - prompt: '' 28 | in: 'Add file' 29 | out: '' 30 | - prompt: '' 31 | in: ':wq' 32 | out: '' 33 | - prompt: '' 34 | in: '' 35 | out: '[master 9d83964] Add file' 36 | - prompt: '' 37 | in: $ 38 | out: '' 39 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/git-config.yml: -------------------------------------------------------------------------------- 1 | speed: 4 2 | lines: 3 | - prompt: $ 4 | in: smol-git config user.name Vinayak 5 | out: '' 6 | - prompt: '$' 7 | in: cat ~/.config/smol_git/config 8 | out: '' 9 | - prompt: '' 10 | in: '' 11 | out: '[user]' 12 | - prompt: '' 13 | in: '' 14 | out: 'name = Vinayak' 15 | - prompt: '' 16 | in: $ 17 | out: '' 18 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/git-log.yml: -------------------------------------------------------------------------------- 1 | speed: 4 2 | lines: 3 | - prompt: $ 4 | in: smol-git log 5 | out: '' 6 | - prompt: '' 7 | in: '' 8 | out: 'commit 6b635e74901e6e4c264534c0f05dc0498e0f5eb6' 9 | - prompt: '' 10 | in: '' 11 | out: 'Author: Vinayak Mehta ' 12 | - prompt: '' 13 | in: '' 14 | out: 'Date: Tue Apr 14 15:45:38 2020 +0530' 15 | - prompt: '' 16 | in: '' 17 | out: ' ' 18 | - prompt: '' 19 | in: '' 20 | out: ' Third commit' 21 | - prompt: '' 22 | in: '' 23 | out: ' ' 24 | - prompt: '' 25 | in: '' 26 | out: 'commit 9a05e2014694f1633a17ed84eab00f577d0a51f2' 27 | - prompt: '' 28 | in: '' 29 | out: 'Author: Vinayak Mehta ' 30 | - prompt: '' 31 | in: '' 32 | out: 'Date: Tue Apr 14 15:45:32 2020 +0530' 33 | - prompt: '' 34 | in: '' 35 | out: ' ' 36 | - prompt: '' 37 | in: '' 38 | out: ' Second commit' 39 | - prompt: '' 40 | in: '' 41 | out: ' ' 42 | - prompt: '' 43 | in: ':' 44 | out: '' 45 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/git-push.yml: -------------------------------------------------------------------------------- 1 | speed: 2 2 | lines: 3 | - prompt: $ 4 | in: smol-git push origin master 5 | out: '' 6 | - prompt: "Username for 'https://github.com':" 7 | in: vinayak 8 | out: '' 9 | - prompt: "Password for 'https://vinayak@github.com':" 10 | in: ' ' 11 | out: '' 12 | - prompt: "" 13 | in: '' 14 | out: "Pushing to 'origin'..." 15 | - prompt: '' 16 | in: ████████████████████████ 17 | out: '' 18 | - prompt: '' 19 | in: $ 20 | out: '' 21 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/git-status.yml: -------------------------------------------------------------------------------- 1 | speed: 4 2 | lines: 3 | - prompt: $ 4 | in: smol-git status 5 | out: '' 6 | - prompt: '' 7 | in: '' 8 | out: 'On branch master' 9 | - prompt: '' 10 | in: '' 11 | out: 'Changes to be committed:' 12 | - prompt: '' 13 | in: '' 14 | out: ' (use "git reset HEAD ..." to unstage)' 15 | - prompt: '' 16 | in: '' 17 | out: ' ' 18 | - prompt: '' 19 | in: '' 20 | out: ' new file: a.txt' 21 | color: green 22 | bold: True 23 | - prompt: '' 24 | in: '' 25 | out: ' ' 26 | - prompt: '' 27 | in: $ 28 | out: '' 29 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/icanon.yml: -------------------------------------------------------------------------------- 1 | speed: 5 2 | lines: 3 | - prompt: $ 4 | in: cat 5 | out: '' 6 | - prompt: '' 7 | in: hello world 8 | out: hello world 9 | - prompt: '' 10 | in: '' 11 | out: ^C 12 | - prompt: $ 13 | in: stty -icanon 14 | out: '' 15 | - prompt: $ 16 | in: cat 17 | out: '' 18 | - prompt: '' 19 | in: hheelllloo wwoorrlldd^C 20 | out: '' 21 | - prompt: $ 22 | in: stty icanon 23 | out: '' 24 | - prompt: $ 25 | in: cat 26 | out: '' 27 | - prompt: '' 28 | in: hello world 29 | out: hello world 30 | - prompt: '' 31 | in: $ 32 | out: '' 33 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/onlcr.yml: -------------------------------------------------------------------------------- 1 | speed: 5 2 | lines: 3 | - prompt: $ 4 | in: ps 5 | out: ' PID TTY TIME CMD' 6 | - prompt: '' 7 | in: '' 8 | out: ' 396 pts/15 00:00:00 ps' 9 | - prompt: '' 10 | in: '' 11 | out: ' 2739 pts/15 00:00:02 fish' 12 | - prompt: '$' 13 | in: 'stty -onlcr' 14 | out: '' 15 | - prompt: '$' 16 | in: 'ps' 17 | out: ' PID TTY TIME CMD' 18 | - prompt: '' 19 | in: '' 20 | out: ' 396 pts/15 00:00:00 ps' 21 | - prompt: '' 22 | in: '' 23 | out: ' 2739 pts/15 00:00:02 fish' 24 | - prompt: '$' 25 | in: 'stty onlcr' 26 | out: '' 27 | - prompt: $ 28 | in: ps 29 | out: ' PID TTY TIME CMD' 30 | - prompt: '' 31 | in: '' 32 | out: ' 396 pts/15 00:00:00 ps' 33 | - prompt: '' 34 | in: '' 35 | out: ' 2739 pts/15 00:00:02 fish' 36 | - prompt: '' 37 | in: $ 38 | out: '' 39 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/codios/progress-bar.yml: -------------------------------------------------------------------------------- 1 | speed: 2 2 | lines: 3 | - prompt: $ 4 | in: smol-git push origin master 5 | out: '' 6 | - prompt: "" 7 | in: '' 8 | out: "Pushing to 'origin'..." 9 | - prompt: '' 10 | in: ████████████████████████ 11 | out: '' 12 | - prompt: '' 13 | in: $ 14 | out: '' 15 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/images/camelot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/gallery/the-hitchhikers-guide-to-clis-in-python/images/camelot.png -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/images/pyconline-au.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/gallery/the-hitchhikers-guide-to-clis-in-python/images/pyconline-au.jpg -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/images/recurse-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/docs/gallery/the-hitchhikers-guide-to-clis-in-python/images/recurse-center.png -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/index.rst: -------------------------------------------------------------------------------- 1 | Gallery 2 | ======= 3 | 4 | The Hitchhiker's Guide to CLIs in Python 5 | ---------------------------------------- 6 | 7 | A talk about terminals and CLIs, and how to make CLIs using Python. 8 | 9 | .. image:: /_static/gallery/the-hitchhikers-guide-to-clis-in-python.png 10 | :align: center 11 | 12 | Made by `Vinayak Mehta `_. 13 | 14 | You can check out all files `here`_. 15 | 16 | .. _here: https://github.com/vinayak-mehta/present/tree/master/docs/gallery/the-hitchhikers-guide-to-clis-in-python 17 | 18 | Markdown slides: 19 | 20 | .. literalinclude:: slides.md 21 | -------------------------------------------------------------------------------- /docs/gallery/the-hitchhikers-guide-to-clis-in-python/slides.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## The Hitchhiker's Guide to CLIs in Python 4 | 5 | Vinayak Mehta 6 | 7 | @vortex_ape 8 | 9 | --- 10 | 11 | $ whoami 12 | 13 | https://github.com/vinayak-mehta 14 | 15 | --- 16 | 17 | ![camelot](images/camelot.png) 18 | 19 | https://github.com/camelot-dev 20 | 21 | --- 22 | 23 | ![rc](images/recurse-center.png) 24 | 25 | www.recurse.com 26 | 27 | --- 28 | 29 | In the beginning ... 30 | 31 | --- 32 | 33 | Typewriter 📝 34 | 35 | --- 36 | 37 | .... . .-.. .-.. --- / .-- --- .-. .-.. -.. -.-.-- 38 | 39 | --- 40 | 41 | Teletypewriter 📞 📝 42 | 43 | --- 44 | 45 | Computers 💻 46 | 47 | --- 48 | 49 | Friden Flexowriter 📞 📝 50 | 51 | --- 52 | 53 | Teletype Model 33 📞 📝 54 | 55 | --- 56 | 57 | Video terminals 📺 📝 58 | 59 | --- 60 | 61 | Terminal emulators 💻 📝 62 | 63 | --- 64 | 65 | teletype 66 | 67 | --- 68 | 69 | (t)ele(ty)pe 70 | 71 | --- 72 | 73 | tty 74 | 75 | --- 76 | 77 | shell 78 | 79 | --- 80 | 81 | ``` 82 | keyboard 83 | \ 84 | \ input 85 | \ 86 | (terminal)- - - - - - - - - -(process) 87 | / 88 | / output 89 | / 90 | display 91 | ``` 92 | 93 | --- 94 | 95 | ``` 96 | keyboard 97 | \ 98 | \ input 99 | \ 100 | (terminal)- - -(termios)- - -(process) 101 | / 102 | / output 103 | / 104 | display 105 | ``` 106 | 107 | --- 108 | 109 | ``` 110 | $ man termios 111 | ``` 112 | 113 | --- 114 | 115 | ``` 116 | $ stty -a 117 | speed 38400 baud; rows 34; columns 166; line = 0; 118 | intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; 119 | -ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr 120 | opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 121 | isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop 122 | ``` 123 | 124 | --- 125 | 126 | ``` 127 | $ man termios 128 | ... 129 | ICANON Enable canonical mode (described below). 130 | ... 131 | ``` 132 | 133 | --- 134 | 135 | ``` 136 | $ stty -icanon 137 | ``` 138 | 139 | --- 140 | 141 | ![codio](codios/icanon.yml) 142 | 143 | --- 144 | 145 | ``` 146 | $ man termios 147 | ... 148 | ONLCR (XSI) Map NL to CR-NL on output. 149 | ... 150 | ``` 151 | 152 | --- 153 | 154 | ![codio](codios/progress-bar.yml) 155 | 156 | --- 157 | 158 | ``` 159 | $ stty -onlcr 160 | ``` 161 | 162 | --- 163 | 164 | ![codio](codios/onlcr.yml) 165 | 166 | --- 167 | 168 | ``` 169 | $ man termios 170 | ... 171 | ECHO Echo input characters. 172 | ... 173 | ``` 174 | 175 | --- 176 | 177 | ``` 178 | $ stty -echo 179 | ``` 180 | 181 | --- 182 | 183 | ![codio](codios/echo.yml) 184 | 185 | --- 186 | 187 | ``` 188 | $ reset 189 | ``` 190 | 191 | --- 192 | 193 | ``` 194 | import termios 195 | ``` 196 | 197 | --- 198 | 199 | Signals 200 | 201 | --- 202 | 203 | In-band signaling 204 | 205 | --- 206 | 207 | Control characters 208 | 209 | --- 210 | 211 | Control characters 212 | 213 | - ^H - backspace 214 | - ^J - newline 215 | - ^C - interrupt the running process 216 | - ^D - end text input or exit the shell 217 | 218 | --- 219 | 220 | Escape sequences 221 | 222 | --- 223 | 224 | Escape sequences 225 | 226 | - \u001b[2J - clear screen 227 | - \u001b[1m - make text bold 228 | - \u001b[31m - make text red 229 | - \u001b[{n}A - moves cursor up by n 230 | 231 | --- 232 | 233 | Streams 234 | 235 | --- 236 | 237 | stdin 238 | 239 | --- 240 | 241 | stdout and stderr 242 | 243 | --- 244 | 245 | Redirection 246 | 247 | --- 248 | 249 | ``` 250 | $ echo "hello" > file 251 | $ echo "world" >> file 252 | ``` 253 | 254 | --- 255 | 256 | ``` 257 | $ echo "hello" | cat 258 | hello 259 | ``` 260 | 261 | --- 262 | 263 | Terminals ❤️ 264 | 265 | --- 266 | 267 | 268 | Command-line interfaces 269 | 270 | --- 271 | 272 | Command-line interfaces 273 | 274 | ``` 275 | Prompt 276 | ``` 277 | 278 | --- 279 | 280 | Command-line interfaces 281 | 282 | ``` 283 | Prompt command 284 | ``` 285 | 286 | --- 287 | 288 | Command-line interfaces 289 | 290 | ``` 291 | Prompt command option1 option2 292 | ``` 293 | 294 | --- 295 | 296 | Command-line interfaces 297 | 298 | ``` 299 | Prompt command option1 option2 argument1 argument2 300 | ``` 301 | 302 | --- 303 | 304 | Command-line interfaces 305 | 306 | ``` 307 | Prompt command option1 option2 argument1 argument2 308 | Output 309 | ``` 310 | 311 | --- 312 | 313 | Arguments 314 | 315 | --- 316 | 317 | Arguments 318 | 319 | ``` 320 | $ cp src dst 321 | ``` 322 | 323 | --- 324 | 325 | Options 326 | 327 | --- 328 | 329 | Options 330 | 331 | ``` 332 | $ cp -r src dst 333 | ``` 334 | 335 | --- 336 | 337 | Help 338 | 339 | --- 340 | 341 | Help 342 | 343 | ``` 344 | $ cp --help 345 | ``` 346 | 347 | --- 348 | 349 | Man pages 350 | 351 | ``` 352 | $ man termios 353 | ``` 354 | 355 | --- 356 | 357 | Standards 358 | 359 | --- 360 | 361 | POSIX 362 | 363 | --- 364 | 365 | XDG base directory specification 366 | 367 | --- 368 | 369 | XDG base directory specification 370 | 371 | - $XDG_CONFIG_HOME=$HOME/.config 372 | - $XDG_DATA_HOME=$HOME/.local/share 373 | - $XDG_CACHE_HOME=$HOME/.cache 374 | 375 | --- 376 | 377 | 378 | CLIs in Python 379 | 380 | --- 381 | 382 | smol-pip 🐤 383 | 384 | --- 385 | 386 | ``` 387 | $ smol-pip install --upgrade package_name 388 | ``` 389 | 390 | --- 391 | 392 | Standard library 393 | 394 | --- 395 | 396 | sys 397 | 398 | --- 399 | 400 | sys.argv 401 | 402 | --- 403 | 404 | ``` 405 | import sys 406 | 407 | help = "Pip Installs Packages." 408 | 409 | if __name__ == "__main__": 410 | arguments = sys.argv 411 | ``` 412 | 413 | --- 414 | 415 | ``` 416 | import sys 417 | 418 | help = "Pip Installs Packages." 419 | 420 | if __name__ == "__main__": 421 | arguments = sys.argv 422 | if arguments[1] in ["-h", "--help"]: 423 | print(help) 424 | ``` 425 | 426 | --- 427 | 428 | ``` 429 | import sys 430 | 431 | help = "Pip Installs Packages." 432 | 433 | if __name__ == "__main__": 434 | arguments = sys.argv 435 | if arguments[1] in ["-h", "--help"]: 436 | print(help) 437 | elif arguments[1] in ["-v", "--version"]: 438 | print("0.1.0") 439 | ``` 440 | 441 | --- 442 | 443 | ``` 444 | import sys 445 | 446 | help = "Pip Installs Packages." 447 | 448 | if __name__ == "__main__": 449 | arguments = sys.argv 450 | ... 451 | else: 452 | print(arguments) 453 | # ['smol-pip', 'install', '--upgrade', 'Click'] 454 | if arguments[1] == "install": 455 | # dispatch to install / upgrade code 456 | else: 457 | raise ValueError("Unknown subcommand!") 458 | ``` 459 | 460 | --- 461 | 462 | optparse 463 | 464 | --- 465 | 466 | PEP 389 467 | 468 | --- 469 | 470 | argparse 471 | 472 | --- 473 | 474 | argparse 475 | 476 | - -pf 477 | - -file 478 | - +f 479 | - +rgb 480 | - /f 481 | - /file 482 | 483 | --- 484 | 485 | argparse 486 | 487 | - pip install 488 | - pip freeze 489 | - pip search 490 | 491 | --- 492 | 493 | ``` 494 | import argparse 495 | 496 | parser = argparse.ArgumentParser( 497 | description="Pip Installs Packages." 498 | ) 499 | ``` 500 | 501 | --- 502 | 503 | ``` 504 | import argparse 505 | 506 | parser = argparse.ArgumentParser( 507 | description="Pip Installs Packages." 508 | ) 509 | parser.add_argument( 510 | "-v", 511 | "--version", 512 | action="version", 513 | version="0.1.0" 514 | ) 515 | ``` 516 | 517 | --- 518 | 519 | ``` 520 | subparsers = parser.add_subparsers(dest="subparser_name") 521 | install = subparsers.add_parser("install") 522 | ``` 523 | 524 | --- 525 | 526 | ``` 527 | subparsers = parser.add_subparsers(dest="subparser_name") 528 | install = subparsers.add_parser("install") 529 | install.add_argument( 530 | "-u", 531 | "--upgrade", 532 | action="store_true", 533 | help="Upgrade package to the newest available version.", 534 | ) 535 | install.add_argument("package_name") 536 | ``` 537 | 538 | --- 539 | 540 | ``` 541 | if __name__ == "__main__": 542 | arguments = parser.parse_args() 543 | print(arguments) 544 | # Namespace(package_name='Click', upgrade=True) 545 | ``` 546 | 547 | --- 548 | 549 | ``` 550 | if __name__ == "__main__": 551 | arguments = parser.parse_args() 552 | print(arguments) 553 | # Namespace(package_name='Click', upgrade=True) 554 | if arguments.subparser_name == "install": 555 | # dispatch to install / upgrade code 556 | else: 557 | raise ValueError("Unknown subcommand!") 558 | ``` 559 | 560 | --- 561 | 562 | ``` 563 | $ smol-pip --help 564 | usage: smol-pip [-h] [-v] {install} ... 565 | 566 | Pip Installs Packages. 567 | 568 | positional arguments: 569 | {install} 570 | 571 | optional arguments: 572 | -h, --help show this help message and exit 573 | -v, --version show program's version number and exit 574 | ``` 575 | 576 | --- 577 | 578 | Python Package Index 579 | 580 | --- 581 | 582 | docopt 583 | 584 | --- 585 | 586 | ``` 587 | help = """Pip Installs Packages. 588 | 589 | Usage: 590 | smol-pip install PACKAGE_NAME 591 | smol-pip install --upgrade PACKAGE_NAME 592 | 593 | Options: 594 | -h --help Show this screen. 595 | --version Show version. 596 | """ 597 | ``` 598 | 599 | --- 600 | 601 | ``` 602 | from docopt import docopt 603 | 604 | if __name__ == "__main__": 605 | arguments = docopt(help, version="0.1.0") 606 | print(arguments) 607 | # {'--upgrade': True, 608 | # 'PACKAGE_NAME': 'Click', 609 | # 'install': True} 610 | ``` 611 | 612 | --- 613 | 614 | ``` 615 | from docopt import docopt 616 | 617 | if __name__ == "__main__": 618 | arguments = docopt(help, version="0.1.0") 619 | print(arguments) 620 | # {'--upgrade': True, 621 | # 'PACKAGE_NAME': 'Click', 622 | # 'install': True} 623 | if arguments["install"]: 624 | # dispatch to install / upgrade code 625 | else: 626 | raise ValueError("Unknown subcommand!") 627 | ``` 628 | 629 | --- 630 | 631 | 632 | 633 | --- 634 | 635 | 636 | click 637 | 638 | --- 639 | 640 | ``` 641 | import click 642 | 643 | def cli(*args, **kwargs): 644 | """Pip Installs Packages.""" 645 | pass 646 | ``` 647 | 648 | --- 649 | 650 | ``` 651 | import click 652 | 653 | @click.group("pip") 654 | def cli(*args, **kwargs): 655 | """Pip Installs Packages.""" 656 | pass 657 | ``` 658 | 659 | --- 660 | 661 | ``` 662 | import click 663 | 664 | @click.group("pip") 665 | @click.version_option("0.1.0") 666 | def cli(*args, **kwargs): 667 | """Pip Installs Packages.""" 668 | pass 669 | ``` 670 | 671 | --- 672 | 673 | ``` 674 | def install(*args, **kwargs): 675 | """Install packages.""" 676 | # install / upgrade package_name 677 | ``` 678 | 679 | --- 680 | 681 | ``` 682 | @cli.command("install") 683 | def install(*args, **kwargs): 684 | """Install packages.""" 685 | # install / upgrade package_name 686 | ``` 687 | 688 | --- 689 | 690 | ``` 691 | @cli.command("install") 692 | @click.option( 693 | "-u", 694 | "--upgrade", 695 | is_flag=True, 696 | help="Upgrade package to the newest available version.", 697 | ) 698 | def install(*args, **kwargs): 699 | """Install packages.""" 700 | # install / upgrade package_name 701 | ``` 702 | 703 | --- 704 | 705 | ``` 706 | @cli.command("install") 707 | @click.option( 708 | "-u", 709 | "--upgrade", 710 | is_flag=True, 711 | help="Upgrade package to the newest available version.", 712 | ) 713 | @click.argument("package_name") 714 | def install(*args, **kwargs): 715 | """Install packages.""" 716 | # install / upgrade package_name 717 | ``` 718 | 719 | --- 720 | 721 | ``` 722 | if __name__ == "__main__": 723 | cli() 724 | ``` 725 | 726 | --- 727 | 728 | ``` 729 | @cli.command("install") 730 | @click.option( 731 | "-u", 732 | "--upgrade", 733 | is_flag=True, 734 | help="Upgrade package to the newest available version.", 735 | ) 736 | @click.argument("package_name") 737 | def install(*args, **kwargs): 738 | """Install packages.""" 739 | print(kwargs) 740 | # {'upgrade': True, 'package_name': 'Click'} 741 | # install / upgrade package_name 742 | ``` 743 | 744 | --- 745 | 746 | ``` 747 | $ smol-pip --help 748 | Usage: smol-pip [OPTIONS] COMMAND [ARGS]... 749 | 750 | Pip Installs Packages. 751 | 752 | Options: 753 | --version Show the version and exit. 754 | --help Show this message and exit. 755 | 756 | Commands: 757 | install Install packages. 758 | ``` 759 | 760 | --- 761 | 762 | click 763 | 764 | --- 765 | 766 | smol-git 🐤 767 | 768 | --- 769 | 770 | ``` 771 | $ smol-git --help 772 | Usage: smol-git [OPTIONS] COMMAND [ARGS]... 773 | 774 | smol-git - the stupid content tracker 775 | 776 | Options: 777 | --version Show the version and exit. 778 | --help Show this message and exit. 779 | 780 | Commands: 781 | clone Clone a repository into a new directory. 782 | commit Record changes to the repository. 783 | config Get and set repository or global options. 784 | log Show commit logs. 785 | push Update remote refs along with associated objects. 786 | status Show the working tree status. 787 | ``` 788 | 789 | --- 790 | 791 | ``` 792 | import click 793 | 794 | @click.group("smol-git") 795 | @click.version_option("0.1.0") 796 | def cli(*args, **kwargs): 797 | """smol-git - the stupid content tracker""" 798 | pass 799 | ``` 800 | 801 | --- 802 | 803 | Progress bars 804 | 805 | --- 806 | 807 | ``` 808 | @cli.command() 809 | @click.argument("src") 810 | @click.argument("dest", required=False) 811 | def clone(src, dest): 812 | ... 813 | with click.progressbar(files) as _files: 814 | for file in _files: 815 | # download file 816 | ``` 817 | 818 | --- 819 | 820 | ![codio](codios/git-clone.yml) 821 | 822 | --- 823 | 824 | Application folders 825 | 826 | --- 827 | 828 | ``` 829 | @cli.command() 830 | @click.argument("key") 831 | @click.argument("value") 832 | def config(key, value): 833 | app_dir = click.get_app_dir("smol_git") 834 | if not os.path.exists(app_dir): 835 | os.makedirs(app_dir) 836 | cfg = os.path.join(app_dir, "config") 837 | # set repository or global options 838 | ``` 839 | 840 | --- 841 | 842 | ![codio](codios/git-config.yml) 843 | 844 | --- 845 | 846 | Paged output 847 | 848 | --- 849 | 850 | ``` 851 | @cli.command() 852 | def log(): 853 | ... 854 | click.echo_via_pager(log_string) 855 | ``` 856 | 857 | --- 858 | 859 | ![codio](codios/git-log.yml) 860 | 861 | --- 862 | 863 | Colored text 864 | 865 | --- 866 | 867 | ``` 868 | @cli.command() 869 | def status(): 870 | ... 871 | for file in files: 872 | file_status = "new file" if file.added else "modified" 873 | status += click.style( 874 | f"\t{file_status}: {file.name}\n", 875 | fg="green", 876 | bold=True 877 | ) 878 | click.echo(status_string) 879 | ``` 880 | 881 | --- 882 | 883 | ![codio](codios/git-status.yml) 884 | 885 | --- 886 | 887 | Launching editors 888 | 889 | --- 890 | 891 | ``` 892 | @cli.command() 893 | @click.option("-m", "--message", help="The commit message.") 894 | def commit(*args, **kwargs): 895 | if kwargs["message"] is None: 896 | commit_message = click.edit() 897 | else: 898 | commit_message = kwargs["message"] 899 | # commit changes 900 | ``` 901 | 902 | --- 903 | 904 | ![codio](codios/git-commit.yml) 905 | 906 | --- 907 | 908 | User prompts 909 | 910 | --- 911 | 912 | ``` 913 | @cli.command() 914 | @click.argument("repository") 915 | @click.argument("branch") 916 | def push(repository, branch): 917 | username = click.prompt("Username for 'https://github.com'") 918 | password = click.prompt( 919 | f"Password for 'https://{username}@github.com'", 920 | hide_input=True 921 | ) 922 | # push changes 923 | ``` 924 | 925 | --- 926 | 927 | ![codio](codios/git-push.yml) 928 | 929 | --- 930 | 931 | ``` 932 | $ smol-git --help 933 | Usage: smol-git [OPTIONS] COMMAND [ARGS]... 934 | 935 | smol-git - the stupid content tracker 936 | 937 | Options: 938 | --version Show the version and exit. 939 | --help Show this message and exit. 940 | 941 | Commands: 942 | clone Clone a repository into a new directory. 943 | commit Record changes to the repository. 944 | config Get and set repository or global options. 945 | log Show commit logs. 946 | push Update remote refs along with associated objects. 947 | status Show the working tree status. 948 | ``` 949 | 950 | --- 951 | 952 | Testing click code 953 | 954 | --- 955 | 956 | ``` 957 | from click.testing import CliRunner 958 | from smol_git.cli import cli 959 | 960 | def test_git_log(): 961 | runner = CliRunner() 962 | result = runner.invoke(cli, ['log']) 963 | assert result.exit_code == 0 964 | assert result.output == expected_output_log 965 | ``` 966 | 967 | --- 968 | 969 | https://click.palletsprojects.com 970 | 971 | --- 972 | 973 | 974 | Packaging the CLI 975 | 976 | --- 977 | 978 | ``` 979 | . 980 | ├── setup.py 981 | └── smol_git 982 | ├── cli.py 983 | ├── __init__.py 984 | ├── utils.py 985 | └── __version__.py 986 | ``` 987 | 988 | --- 989 | 990 | ``` 991 | from setuptools import setup 992 | 993 | setup( 994 | ... 995 | name="smol-git", 996 | entry_points={ 997 | "console_scripts": [ 998 | "smol-git = smol_git.cli:cli" 999 | ] 1000 | }, 1001 | ... 1002 | ) 1003 | ``` 1004 | 1005 | --- 1006 | 1007 | Pushing to PyPI 1008 | 1009 | --- 1010 | 1011 | ``` 1012 | $ python setup.py sdist bdist_wheel 1013 | $ twine upload dist/* 1014 | ``` 1015 | 1016 | --- 1017 | 1018 | 1019 | User experience 1020 | 1021 | --- 1022 | 1023 | Unix philosophy 1024 | 1025 | - Write programs that do one thing and do it well. 1026 | - Write programs to work together. 1027 | - Write programs to handle text streams, because that is a universal interface. 1028 | 1029 | --- 1030 | 1031 | Make features discoverable 1032 | 1033 | - Persistent history 1034 | - History search 1035 | - Auto-completion 1036 | 1037 | --- 1038 | 1039 | ## prompt-toolkit 1040 | 1041 | - ipython 1042 | - dbcli 1043 | 1044 | --- 1045 | 1046 | Resources 1047 | 1048 | - Slides: https://vinayak.io/talks 1049 | - Code: https://github.com/vinayak-mehta/smol-git 1050 | - The TTY demystified 1051 | - What is the exact difference between a terminal, a shell, a tty and a console? 1052 | - Brandon Rhodes' keynote at North Bay Python 2017 1053 | - Terminal whispering by Thomas Ballinger at PyCon 2015 1054 | - Writing Command Line Applications that Click by Dave Forgac at PyCon 2019 1055 | - Awesome CLI Tools by Amjith Ramanujam at PyCon 2017 1056 | - Fish shell design document 1057 | 1058 | --- 1059 | 1060 | 1061 | @vortex_ape / vinayak.io 1062 | 1063 | Made with ❤️ using 'present' 1064 | 1065 | https://github.com/vinayak-mehta/present 1066 | 1067 | --- 1068 | 1069 | ![pyconline-au](images/pyconline-au.jpg) 1070 | -------------------------------------------------------------------------------- /docs/gallery/trustless-bridges/index.rst: -------------------------------------------------------------------------------- 1 | Gallery 2 | ======= 3 | 4 | Trustless Bridges 5 | ----------------- 6 | 7 | A presentation about trustless blockchain bridges built using the Substrate blockchain building framework. Bridges are a way to connect different blockchains without a trusted central party. 8 | 9 | .. image:: /_static/gallery/trustless-bridges.gif 10 | :align: center 11 | 12 | Made by `Hernando Castano `_. 13 | 14 | You can check out all files `here`_. 15 | 16 | .. _here: https://github.com/vinayak-mehta/present/tree/master/docs/gallery/trustless-bridges 17 | 18 | Markdown slides: 19 | 20 | .. literalinclude:: slides.md 21 | -------------------------------------------------------------------------------- /docs/gallery/trustless-bridges/slides.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Trustless Bridges 4 | 5 | ### By: Hernando Castano | @hernando:matrix.parity.io 6 | 7 | ``` 8 | ^^ 9 | ^^ .. .. 10 | [] [] 11 | .:[]:_ ^^ ,:[]:. 12 | .: :[]: :-. ,-: :[]: :. 13 | .: : :[]: : :`._ ,.': : :[]: : :. 14 | .: : : :[]: : : : :-._ _,-: : : : :[]: : : :. 15 | _..: : : : :[]: : : : : : :-._________.-: : : : : : :[]: : : : :-._ 16 | _:_:_:_:_:_:[]:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:_:[]:_:_:_:_:_:_ 17 | !!!!!!!!!!!![]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!![]!!!!!!!!!!!!! 18 | ^^^^^^^^^^^^[]^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[]^^^^^^^^^^^^^ 19 | [] [] 20 | [] [] 21 | [] [] 22 | ~~^-~^_~^~/ \~^-~^~_~^-~_^~-^~_^~~-^~_~^~-~_~-^~_^/ \~^-~_~^-~~- 23 | ~ _~~- ~^-^~-^~~- ^~_^-^~~_ -~^_ -~_-~~^- _~~_~-^_ ~^-^~~-_^-~ ~^ 24 | ~ ^- _~~_- ~~ _ ~ ^~ - ~~^ _ - ^~- ~ _ ~~^ - ~_ - ~^_~ 25 | ~- ^_ ~^ - ^~ _ - ~^~ _ _~^~- _ ~~^ - _ ~ - _ ~~^ - 26 | jgs ~^ -_ ~^^ -_ ~ _ - _ ~^~- _~ -_ ~- _ ~^ _ - ~ ^- 27 | ~^~ - _ ^ - ~~~ _ - _ ~-^ ~ __- ~_ - ~ ~^_- 28 | ~ ~- ^~ - ~^ - ~ ^~ - ~~ ^~ - ~ 29 | ``` 30 | 31 | --- 32 | 33 | 34 | ## Presentation Outline 35 | 36 | - What is a Bridge? 37 | - Types of Bridges 38 | - Rialto Bridge 39 | - Future Plans 40 | 41 | --- 42 | 43 | 44 | ## Demo Prep - RLT 45 | 46 | --- 47 | 48 | 49 | ## What is a Bridge? 50 | 51 | tl;dr: A way to connect two unrelated chains 52 | 53 | ### Types of Bridges 54 | - Custodial 55 | - Collateral 56 | - Trustless 57 | 58 | --- 59 | 60 | 61 | ## Custodial Bridges 62 | 63 | [Chain A] -> [Central Party] -> [Chain B] 64 | 65 | - You send your Chain A token to the central party 66 | - Expectation is that they will credit your Chain B account correctly 67 | 68 | [BTC] -> [wBTC Foundation] -> [ETH] 69 | 70 | - Example: Wrapped BTC on Ethereum 71 | 72 | --- 73 | 74 | 75 | ## Collateral Backed Bridges (XClaim) 76 | 77 | +----------------------+ +----------------------+ 78 | | BTC | | ETH | 79 | | +-------+ | | +-------+ | 80 | | 1 | | | 0 | | | | 81 | | +----> | Vault | +--------------> | S.C | +--+ | 82 | | | | | | | | | | | 83 | | | +-------+ | | +-------+ | 3 | 84 | | | | | | | 85 | | | | | ^ v | 86 | | + | 2 | | | 87 | | Alice +----------------------------------+ Alice | 88 | | | | | 89 | +----------------------+ +----------------------+ 90 | 91 | --- 92 | 93 | 94 | ## Trustless Bridges 95 | 96 | [Chain A] -> [Chain B] 97 | 98 | - Basically an on-chain light client for a foreign chain 99 | - Light clients only follow headers and keep no state 100 | 101 | [Chain A Headers] -> [Chain B] 102 | 103 | - A light client will track headers of the source chain 104 | - If applicable, needs to keep track of finality 105 | 106 | --- 107 | 108 | 109 | # Rialto Bridge 110 | 111 | --- 112 | 113 | 114 | ## Rialto Bridge 115 | 116 | [PoA Ethereum] <-> [Substrate] 117 | 118 | - Two way trustless bridge between an Ethereum PoA chain and a Substrate chain 119 | - Ethereum chain is using Aura consensus 120 | - Substrate chain is using Grandpa consensus 121 | - Repo: https://github.com/paritytech/parity-bridges-common 122 | 123 | --- 124 | 125 | 126 | ## Authority Round (Aura) Consensus 127 | 128 | - Designed in Parity Ethereum days for stable a test network 129 | - Blocks are produced at regular intervals by trusted authorities 130 | - Authorities implicitly vote on blocks by building on blocks 131 | - A block is considered finalized if it has (n/2) + 1 votes 132 | - If we had three validators: (3/2) + 1 = 2 133 | 134 | [A] <- [B] <- [C] 135 | | | 136 | | --- Finalized when C is built 137 | --- Finalized when B is built 138 | 139 | --- 140 | 141 | 142 | ## Grandpa Consensus 143 | 144 | - Authorities are staked 145 | - Authorities vote on chains of blocks which they think are final 146 | - Authority sets change periodically 147 | - Blocks that signal these changes have a special log in the header 148 | 149 | ``` 150 | / [C1] <- [D1] 151 | [A] <- [B] <- [C2] <- [D2] 152 | \ [D3] <- [E3] 153 | ``` 154 | 155 | --- 156 | 157 | 158 | ## Rialto Architecture 159 | 160 | OpenEthereum Node Substrate Node 161 | 162 | +-------------------+ +-------------------+ 163 | | | | | 164 | | Substrate Header | | Ethereum Bridge | 165 | | Light Client | | Pallet | 166 | | | | | 167 | | | | Currency Exchange| 168 | | Grandpa Finality | | Pallet | 169 | | Precompile | | | 170 | | | | | 171 | | | | | 172 | | | | | 173 | +-------------------+ +-------------------+ 174 | 175 | --- 176 | 177 | 178 | ## Bridge Relay 179 | 180 | +--------+ +--------+ 181 | | | | | 182 | | ETH | | SUB | 183 | | | | | 184 | +--+----+ +-----+-+ 185 | ^ ^ 186 | | | 187 | | +--------+ | 188 | RPC | | | | RPC 189 | | | Relay | | 190 | +--> | | <--+ 191 | +--------+ 192 | 193 | --- 194 | 195 | 196 | ## Rialto Applications 197 | 198 | +----------------------------------------| 199 | | | 200 | | Currency Exchange | 201 | | | 202 | +----------------------------------------+ 203 | | | 204 | | Light Client Base Layer | 205 | | | 206 | +----------------------------------------+ 207 | 208 | --- 209 | 210 | 211 | ## Rialto Applications 212 | 213 | +----------------------------------------+ 214 | | | 215 | | Your Application! | 216 | | | 217 | +----------------------------------------+ 218 | | | 219 | | Message Dispatch | 220 | | | 221 | +----------------------------------------+ 222 | | | 223 | | Message Delivery | 224 | | | 225 | +----------------------------------------+ 226 | | | 227 | | Light Client Base Layer | 228 | | | 229 | +----------------------------------------+ 230 | 231 | --- 232 | 233 | 234 | ## Eth2Sub Header Sync 235 | 236 | ``` 237 | Rialto PoA Bridge Relay Substrate 238 | 239 | + + + 240 | | | Best ETH header? | 241 | | | +------------------> | 242 | | | | 243 | | | I know about X | 244 | | Any newer header? | <------------------+ | 245 | | <------------------+ | | 246 | | | | 247 | | | | 248 | | Yep, here you go | | 249 | | +------------------> | | 250 | | | New headers incoming | 251 | | | +------------------> | 252 | | | | 253 | | | | +----+ 254 | | | | | Verify headers 255 | | | | <----+ 256 | | | | 257 | | | | +----+ 258 | | | | | Import headers 259 | | | | <----+ 260 | | | | 261 | + + + 262 | ``` 263 | --- 264 | 265 | 266 | ## Eth2Sub Currency Exchange 267 | 268 | ``` 269 | Rialto PoA Bridge Relay Substrate 270 | 271 | Lock RLT + + + 272 | +-----+ | | | 273 | | | | | 274 | +-----> | | | 275 | | Any CE Txs? | | 276 | | <------------------+ | | 277 | | | | 278 | | Yep, here ya go | | 279 | | +------------------> | | 280 | | | Tx Inclusion Proof | 281 | | | +------------------> | +-----+ 282 | | | | | In finalized header? 283 | | | | <-----+ 284 | | | | +-----+ 285 | | | | | Credit Funds 286 | | | | <-----+ 287 | + + + 288 | ``` 289 | 290 | --- 291 | 292 | 293 | # Future Plans 294 | --- 295 | 296 | 297 | ## Future Plans 298 | 299 | [DOT] <-> [KSM] 300 | 301 | - Want to connect Substrate based chains 302 | - Important for sovereign chain communication 303 | - Potentially implement a bridge parachain 304 | 305 | [DOT::Call] <-> [KSM::Call] 306 | 307 | - Arbitrary message passing between Substrate chains 308 | 309 | [ETH] <-> [DOT] 310 | 311 | - Want to connect Ethereum mainnet to Substrate chains 312 | - More of a support role to other teams (e.g Snowfork) 313 | 314 | [DOT] <-> [DOGE] 315 | 316 | - Who wouldn't want to see this? 317 | 318 | --- 319 | 320 | 321 | # Shout-Outs 322 | Slava Nikolsky (@svyatonik) 323 | Tomek Drwięga (@tomusdrw) 324 | Thibaut Sardan (@tbaut) 325 | -------------------------------------------------------------------------------- /docs/gallery/welcome-to-coding/codios/start_class.yml: -------------------------------------------------------------------------------- 1 | speed: 6 2 | lines: 3 | - prompt: Mr.H $ 4 | in: pwd 5 | color: green 6 | - out: ' home' 7 | color: green 8 | bold: True 9 | - prompt: Mr.H $ 10 | in: cd DATA/coding/ 11 | color: green 12 | - out: ' cwd = DATA/coding/' 13 | color: green 14 | bold: True 15 | - prompt: Mr.H $ 16 | in: run --start_class 17 | color: green 18 | - out: ' Please Login to Canvas' 19 | color: green 20 | bold: True 21 | - out: ' Students are logging in' 22 | color: green 23 | bold: True 24 | - progress: true 25 | progressChar: █ 26 | -------------------------------------------------------------------------------- /docs/gallery/welcome-to-coding/index.rst: -------------------------------------------------------------------------------- 1 | Gallery 2 | ======= 3 | 4 | Welcome to Coding 5 | ----------------- 6 | 7 | I teach coding at a middle school. I made this welcome slide for students to see as they join our zoom class. 8 | 9 | .. image:: /_static/gallery/welcome-to-coding.gif 10 | :align: center 11 | 12 | Made by `Russell Helmstedter `_. 13 | 14 | You can check out all files `here`_. 15 | 16 | .. _here: https://github.com/vinayak-mehta/present/tree/master/docs/gallery/welcome-to-coding 17 | 18 | Markdown slides: 19 | 20 | .. literalinclude:: slides.md 21 | -------------------------------------------------------------------------------- /docs/gallery/welcome-to-coding/slides.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Welcome to Coding 4 | 5 | ![codio](codios/start_class.yml) 6 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. present documentation master file, created by 2 | sphinx-quickstart on Sat Aug 1 03:02:35 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | present — A terminal-based presentation tool with colors and effects 7 | ==================================================================== 8 | 9 | .. image:: https://readthedocs.org/projects/present/badge/?version=latest 10 | :target: https://present.readthedocs.io/en/latest/ 11 | :alt: Documentation Status 12 | 13 | .. image:: https://img.shields.io/pypi/v/present.svg 14 | :target: https://pypi.org/project/present/ 15 | 16 | .. image:: https://img.shields.io/pypi/pyversions/present.svg 17 | :target: https://pypi.org/project/present/ 18 | 19 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 20 | :target: https://github.com/ambv/black 21 | 22 | .. image:: https://repl.it/badge/github/vinayak-mehta/present 23 | :target: https://repl.it/@amasad/terminal-present 24 | 25 | ``present`` is a terminal-based presentation tool with colors and effects. 26 | 27 | .. image:: _static/demo.gif 28 | 29 | You can also play a :ref:`codio` (pre-recorded code block) on a slide. 30 | 31 | .. image:: _static/codio.gif 32 | 33 | ``present`` is built on `asciimatics `_, and it works with ``Python>=3.7``. 34 | 35 | Check out the :ref:`gallery` to see what everyone is making with ``present``! You can add your slides by simply `opening an issue`_. 36 | 37 | .. _opening an issue: https://github.com/vinayak-mehta/present/issues/new?assignees=&labels=made-with-present&template=submit-slides-to-gallery.md&title=Add+slides+to+gallery 38 | 39 | Installation 40 | ------------ 41 | 42 | You can simply use pip to install ``present``:: 43 | 44 | $ pip install present 45 | 46 | Usage 47 | ----- 48 | 49 | .. code-block:: bash 50 | 51 | $ present sample.md 52 | 53 | Some controls: 54 | 55 | - Quit: ``q`` 56 | - Previous slide: ``b``, Left arrow, Page Up 57 | - Next slide: ``n``, Space bar, Right arrow, Page Down 58 | 59 | At the end, you can press ``r`` to restart the presentation. 60 | 61 | Syntax 62 | ------ 63 | 64 | Slides follow `Markdown `_ syntax. You can check out the `sample slides `_ for reference. 65 | 66 | .. note:: Some things aren't supported yet: 67 | 68 | - Effects and colors on the same slide. 69 | - Effects and code on the same slide. 70 | 71 | Separator 72 | ^^^^^^^^^ 73 | 74 | Each slide can be separated with a ``---``. 75 | 76 | .. code-block:: 77 | 78 | Slide 1 79 | 80 | --- 81 | 82 | Slide 2 83 | 84 | Headers 85 | ^^^^^^^ 86 | 87 | Level 1 headings become figlets, level 2 headings get underlined with `-`, and level 3 headings become bold. 88 | 89 | .. code-block:: 90 | 91 | # Heading 1 92 | 93 | ## Heading 2 94 | 95 | ### Heading 3 96 | 97 | Text 98 | ^^^^ 99 | 100 | .. code-block:: 101 | 102 | This is normal text 103 | 104 | This is **bold text** 105 | 106 | This is `inline code` 107 | 108 | This is a [link](www.google.com) 109 | 110 | As Kanye West said: 111 | 112 | > We're living the future so 113 | > the present is our past. 114 | 115 | Lists 116 | ^^^^^ 117 | 118 | Ordered lists become unordered lists automatically. 119 | 120 | .. code-block:: 121 | 122 | - Item 1 123 | - Item 1a 124 | - Item 1b 125 | - Item 1c 126 | - Item 2 127 | - Item 2a 128 | 129 | Images 130 | ^^^^^^ 131 | 132 | Image paths are relative to the directory where your slides are kept, and where you invoke `present`. 133 | 134 | .. code-block:: 135 | 136 | ![RC](images/recurse.png) 137 | 138 | .. note:: 139 | 140 | You can use high resolution images and tweak the terminal font size to get the best results. 141 | 142 | Code blocks 143 | ^^^^^^^^^^^ 144 | 145 | .. code-block:: 146 | 147 | ``` 148 | import os 149 | 150 | os.getcwd() 151 | ``` 152 | 153 | Codios 154 | ^^^^^^ 155 | 156 | Codios are pre-recorded playable code blocks which can be useful for live demos. You can find out how to write one in the :ref:`codio` section. 157 | 158 | .. code-block:: 159 | 160 | ![codio](codio.yml) 161 | 162 | Style 163 | ^^^^^ 164 | 165 | Each slide can be styled with foreground / background colors and effects. By default, slides are black on white with no effects. You can add style to a slide by adding a comment at the beginning of the slide (after the slide separator): 166 | 167 | .. code-block:: 168 | 169 | Slide 1 170 | 171 | --- 172 | 173 | 174 | Slide 2 175 | 176 | --- 177 | 178 | 179 | Slide 3 180 | 181 | Colors: ``black``, ``red``, ``green``, ``yellow``, ``blue``, ``magenta``, ``cyan``, ``white``. 182 | 183 | Effects: ``fireworks``, ``explosions``, ``stars``, ``matrix``, ``plasma``. More coming soon! 184 | 185 | Contributing 186 | ------------ 187 | 188 | The :ref:`contributing` has detailed information about contributing issues, documentation, code, and tests. 189 | 190 | Versioning 191 | ---------- 192 | 193 | ``present`` uses `Semantic Versioning `_. For the available versions, see the tags on the GitHub repository. 194 | 195 | License 196 | ------- 197 | 198 | This project is licensed under the Apache License, see the `LICENSE `_ file for details. 199 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /examples/codio.md: -------------------------------------------------------------------------------- 1 | # codio 2 | 3 | You can play code! 4 | 5 | Press n, right arrow, or space bar to go to the next slide. 6 | 7 | --- 8 | 9 | Press r to reset. 10 | 11 | ![codio](codio.yml) 12 | -------------------------------------------------------------------------------- /examples/codio.yml: -------------------------------------------------------------------------------- 1 | speed: 10 2 | lines: 3 | - prompt: $ 4 | in: touch a.txt 5 | - prompt: $ 6 | in: smol-git status 7 | - out: 'On branch master' 8 | - out: 'Changes to be committed:' 9 | - out: ' (use "git reset HEAD ..." to unstage)' 10 | - out: ' ' 11 | - out: ' new file: a.txt' 12 | color: green 13 | bold: True 14 | - out: ' ' 15 | - prompt: $ 16 | in: smol-git add a.txt 17 | - prompt: $ 18 | in: 'smol-git commit -m "Add a.txt"' 19 | - out: '[master b0faa5a] Add a.txt' 20 | - prompt: $ 21 | in: smol-git push origin master 22 | out: "Pushing to 'origin'..." 23 | - progress: true 24 | progressChar: █ 25 | - prompt: $ 26 | -------------------------------------------------------------------------------- /examples/images/recurse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/examples/images/recurse.png -------------------------------------------------------------------------------- /examples/sample.md: -------------------------------------------------------------------------------- 1 | # present 2 | 3 | A terminal-based presentation tool with colors and effects 4 | 5 | ```bash 6 | $ pip install present 7 | ``` 8 | 9 | --- 10 | 11 | ## Syntax 12 | 13 | You can write slides in Markdown 14 | 15 | Each slide should be separated with a --- 16 | 17 | --- 18 | 19 | ## Lists 20 | 21 | Some controls: 22 | 23 | - Basic 24 | - Quit: q 25 | - Previous slide: b, Left arrow, Page Up 26 | - Next slide: n, Space bar, Right arrow, Page Down 27 | 28 | --- 29 | 30 | ## Formatted text 31 | 32 | As Kanye West said: 33 | 34 | > We're living the future so 35 | > the present is our past. 36 | 37 | This is normal text 38 | 39 | This is **bold text** 40 | 41 | This is `inline code` 42 | 43 | This is a [link](www.google.com) 44 | 45 | --- 46 | 47 | ## Code blocks 48 | 49 | An explanation for the code below: 50 | 51 | ```python 52 | import os 53 | 54 | print(os.getcwd()) 55 | ``` 56 | 57 | ```python 58 | import shutil 59 | 60 | columns, rows = shutil.get_terminal_size() 61 | ``` 62 | 63 | --- 64 | 65 | ## Images 66 | 67 | ``` 68 | ![RC](images/recurse.png) 69 | ``` 70 | 71 | ![RC](images/recurse.png) 72 | 73 | --- 74 | 75 | 76 | ## Colors 77 | 78 | You can style your slides with fg and bg colors! 79 | 80 | Just add this to the top of your slide: 81 | 82 | ```html 83 | 84 | ``` 85 | 86 | Colors: black, red, green, yellow, blue, magenta, cyan, white 87 | 88 | --- 89 | 90 | 91 | ## Effects 92 | 93 | You can also add effects! 94 | 95 | Just add this to the top of your slide: 96 | 97 | ```html 98 | 99 | ``` 100 | 101 | Effects: fireworks, explosions, matrix, plasma, stars, more coming soon! 102 | 103 | --- 104 | 105 | 106 | --- 107 | 108 | 109 | --- 110 | 111 | 112 | --- 113 | 114 | 115 | --- 116 | 117 | -------------------------------------------------------------------------------- /present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinayak-mehta/present/a63abbb2bf8ca6783f37c59e07ab24040562ae77/present.png -------------------------------------------------------------------------------- /present/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .__version__ import __version__ 4 | -------------------------------------------------------------------------------- /present/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import 4 | 5 | 6 | __all__ = ("main",) 7 | 8 | 9 | def main(): 10 | from present.cli import cli 11 | 12 | cli() 13 | 14 | 15 | if __name__ == "__main__": 16 | main() 17 | -------------------------------------------------------------------------------- /present/__version__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | VERSION = (0, 6, 0) 4 | PRERELEASE = None # alpha, beta or rc 5 | REVISION = None 6 | 7 | 8 | def generate_version(version, prerelease=None, revision=None): 9 | version_parts = [".".join(map(str, version))] 10 | if prerelease is not None: 11 | version_parts.append("-{}".format(prerelease)) 12 | if revision is not None: 13 | version_parts.append(".{}".format(revision)) 14 | return "".join(version_parts) 15 | 16 | 17 | __title__ = "present" 18 | __description__ = "A terminal-based presentation tool with colors and effects." 19 | __url__ = "https://github.com/vinayak-mehta/present" 20 | __version__ = generate_version(VERSION, prerelease=PRERELEASE, revision=REVISION) 21 | __author__ = "Vinayak Mehta" 22 | __author_email__ = "vmehta94@gmail.com" 23 | __license__ = "Apache 2.0" 24 | -------------------------------------------------------------------------------- /present/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import click 5 | 6 | from .markdown import Markdown 7 | from .slideshow import Slideshow 8 | 9 | 10 | @click.command() 11 | @click.argument("filename", type=click.Path(exists=True)) 12 | def cli(filename): 13 | """present: A terminal-based presentation tool with colors and effects.""" 14 | 15 | slides = Markdown(filename).parse() 16 | 17 | with Slideshow(slides) as show: 18 | show.play() 19 | 20 | click.secho("All done! ✨ 🍰 ✨", bold=True) 21 | -------------------------------------------------------------------------------- /present/effects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from random import randint, choice 4 | 5 | from asciimatics.effects import Print 6 | from asciimatics.screen import Screen 7 | from asciimatics.effects import Stars, Matrix 8 | from asciimatics.particles import Explosion, StarFirework 9 | from asciimatics.renderers import ( 10 | Plasma, 11 | SpeechBubble, 12 | StaticRenderer, 13 | ColourImageFile, 14 | DynamicRenderer, 15 | ) 16 | 17 | 18 | ATTRS = { 19 | "bold": Screen.A_BOLD, 20 | "normal": Screen.A_NORMAL, 21 | "reverse": Screen.A_REVERSE, 22 | "underline": Screen.A_UNDERLINE, 23 | } 24 | COLORS = { 25 | "black": Screen.COLOUR_BLACK, 26 | "red": Screen.COLOUR_RED, 27 | "green": Screen.COLOUR_GREEN, 28 | "yellow": Screen.COLOUR_YELLOW, 29 | "blue": Screen.COLOUR_BLUE, 30 | "magenta": Screen.COLOUR_MAGENTA, 31 | "cyan": Screen.COLOUR_CYAN, 32 | "white": Screen.COLOUR_WHITE, 33 | } 34 | EFFECTS = ["fireworks", "explosions", "stars", "matrix", "plasma"] 35 | 36 | 37 | class Text(StaticRenderer): 38 | def __init__(self, text): 39 | super(Text, self).__init__() 40 | self._images = [text] 41 | 42 | 43 | class Codio(DynamicRenderer): 44 | def __init__(self, code=None, height=100, width=100): 45 | super(Codio, self).__init__(height, width) 46 | self._code = code 47 | self._height = height 48 | self._width = width 49 | self._state = None 50 | self._reset() 51 | 52 | def _reset(self): 53 | self._state = { 54 | i: {"len": 0, "start": False, "end": False} for i in range(len(self._code)) 55 | } 56 | 57 | def _get_code(self, i): 58 | if self._state.get(i - 1) is None: 59 | self._state[i]["start"] = True 60 | 61 | if self._code[i]["in"]: 62 | if self._state[i]["len"] == len(self._code[i]["in"]): 63 | self._state[i]["end"] = True 64 | return self._code[i]["in"], self._code[i]["out"] 65 | 66 | if self._state.get(i - 1) is not None and not self._state[i - 1]["end"]: 67 | return None, None 68 | else: 69 | c = self._code[i]["in"][: self._state[i]["len"]] 70 | self._state[i]["len"] += 1 71 | return c, None 72 | 73 | if not self._code[i]["in"] and self._code[i]["out"]: 74 | if self._state.get(i - 1) is not None and not self._state[i - 1]["end"]: 75 | return None, None 76 | else: 77 | self._state[i]["end"] = True 78 | return None, self._code[i]["out"] 79 | 80 | def _render_now(self): 81 | x = y = 1 82 | 83 | for i, c in enumerate(self._code): 84 | kwargs = {} 85 | 86 | if self._code[i].get("color") is not None: 87 | kwargs.update({"colour": COLORS[self._code[i]["color"]]}) 88 | 89 | if self._code[i].get("bold") is not None and self._code[i]["bold"]: 90 | kwargs.update({"attr": ATTRS["bold"]}) 91 | 92 | if ( 93 | self._code[i].get("underline") is not None 94 | and self._code[i]["underline"] 95 | ): 96 | kwargs.update({"attr": ATTRS["underline"]}) 97 | 98 | inp, out = self._get_code(i) 99 | if inp is not None: 100 | prompt = self._code[i]["prompt"] 101 | if prompt: 102 | self._write(f"{prompt} {inp}", x, y, **kwargs) 103 | else: 104 | self._write(f"{inp}", x, y, **kwargs) 105 | y += 1 106 | if out is not None and out: 107 | self._write(out, x, y, **kwargs) 108 | y += 1 109 | 110 | return self._plain_image, self._colour_map 111 | 112 | 113 | def _reset(screen): 114 | reset = Print( 115 | screen, 116 | SpeechBubble("Press 'r' to restart."), 117 | int(screen.height / 2) - 2, 118 | attr=ATTRS["bold"], 119 | ) 120 | return [reset] 121 | 122 | 123 | def _base(screen, element, row, fg_color, bg_color, attr=0): 124 | # for heading, text, list, blockhtml 125 | if element.type == "heading" and element.obj["level"] == 3: 126 | attr = ATTRS["bold"] 127 | 128 | base = Print( 129 | screen, 130 | Text(element.render()), 131 | row, 132 | colour=fg_color, 133 | bg=bg_color, 134 | attr=attr, 135 | transparent=False, 136 | ) 137 | 138 | return [base] 139 | 140 | 141 | def _code(screen, element, row): 142 | code = Print( 143 | screen, 144 | Text(element.render()), 145 | row, 146 | colour=Screen.COLOUR_WHITE, 147 | bg=Screen.COLOUR_BLACK, 148 | transparent=False, 149 | ) 150 | 151 | return [code] 152 | 153 | 154 | def _codio(screen, element, row): 155 | codio = Print( 156 | screen, 157 | Codio(code=element.render(), width=element.width, height=element.size), 158 | row, 159 | colour=Screen.COLOUR_WHITE, 160 | bg=Screen.COLOUR_BLACK, 161 | transparent=False, 162 | speed=element.speed, 163 | ) 164 | 165 | return [codio] 166 | 167 | 168 | def _image(screen, element, row, bg_color): 169 | image = Print( 170 | screen, 171 | ColourImageFile( 172 | screen, 173 | element.obj["src"], 174 | element.size, 175 | bg=bg_color, 176 | fill_background=True, 177 | uni=screen.unicode_aware, 178 | dither=screen.unicode_aware, 179 | ), 180 | row, 181 | ) 182 | 183 | return [image] 184 | 185 | 186 | def _fireworks(screen): 187 | effects = [] 188 | x_regions = [ 189 | (0, screen.width), 190 | (0, screen.width // 3), 191 | (screen.width // 3 * 2, screen.width), 192 | ] 193 | y_regions = [ 194 | (0, screen.height), 195 | (screen.height // 2, screen.height), 196 | ] 197 | 198 | for _ in range(20): 199 | x = randint(*choice(x_regions)) 200 | y = randint(*choice(y_regions)) 201 | effects.insert( 202 | 1, 203 | StarFirework( 204 | screen, 205 | x, 206 | y, 207 | randint(25, 30), 208 | start_frame=randint(0, 250), 209 | ), 210 | ) 211 | return effects 212 | 213 | 214 | def _explosions(screen): 215 | effects = [] 216 | x_regions = [ 217 | (0, screen.width), 218 | (0, screen.width // 3), 219 | (screen.width // 3 * 2, screen.width), 220 | ] 221 | y_regions = [ 222 | (0, screen.height), 223 | (screen.height // 2, screen.height), 224 | ] 225 | 226 | for _ in range(20): 227 | x = randint(*choice(x_regions)) 228 | y = randint(*choice(y_regions)) 229 | effects.append( 230 | Explosion( 231 | screen, 232 | x, 233 | y, 234 | randint(20, 30), 235 | start_frame=randint(0, 250), 236 | ) 237 | ) 238 | return effects 239 | 240 | 241 | def _stars(screen): 242 | return [Stars(screen, (screen.width + screen.height) // 2, stop_frame=100)] 243 | 244 | 245 | def _matrix(screen): 246 | return [Matrix(screen, stop_frame=200)] 247 | 248 | 249 | def _plasma(screen): 250 | return [ 251 | Print( 252 | screen, 253 | Plasma(screen.height, screen.width, screen.colours), 254 | 0, 255 | speed=1, 256 | transparent=False, 257 | ) 258 | ] 259 | -------------------------------------------------------------------------------- /present/markdown.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import warnings 5 | 6 | import yaml 7 | from mistune import markdown 8 | 9 | from .slide import ( 10 | Slide, 11 | Heading, 12 | Paragraph, 13 | Text, 14 | Strong, 15 | Codespan, 16 | Emphasis, 17 | Link, 18 | List, 19 | Image, 20 | Codio, 21 | BlockCode, 22 | BlockHtml, 23 | BlockQuote, 24 | ) 25 | 26 | 27 | class Markdown(object): 28 | """Parse and traverse through the markdown abstract syntax tree.""" 29 | 30 | def __init__(self, filename): 31 | self.filename = filename 32 | self.dirname = os.path.dirname(os.path.realpath(filename)) 33 | 34 | def parse(self): 35 | with open(self.filename, "r") as f: 36 | text = f.read() 37 | 38 | slides = [] 39 | ast = markdown(text, renderer="ast") 40 | 41 | sliden = 0 42 | buffer = [] 43 | for i, obj in enumerate(ast): 44 | if obj["type"] in ["newline"]: 45 | continue 46 | 47 | if obj["type"] == "thematic_break" and buffer: 48 | slides.append(Slide(elements=buffer)) 49 | sliden += 1 50 | buffer = [] 51 | continue 52 | 53 | try: 54 | if obj["type"] == "paragraph": 55 | images = [c for c in obj["children"] if c["type"] == "image"] 56 | not_images = [c for c in obj["children"] if c["type"] != "image"] 57 | 58 | for image in images: 59 | image["src"] = os.path.join(self.dirname, os.path.expanduser(image["src"])) 60 | 61 | if image["alt"] == "codio": 62 | with open(image["src"], "r") as f: 63 | codio = yaml.load(f, Loader=yaml.Loader) 64 | buffer.append(Codio(obj=codio)) 65 | else: 66 | buffer.append(Image(obj=image)) 67 | 68 | obj["children"] = not_images 69 | buffer.append(Paragraph(obj=obj)) 70 | else: 71 | element_name = obj["type"].title().replace("_", "") 72 | Element = eval(element_name) 73 | buffer.append(Element(obj=obj)) 74 | except NameError: 75 | warnings.warn(f"(Slide {sliden + 1}) {element_name} is not supported") 76 | 77 | if i == len(ast) - 1: 78 | slides.append(Slide(elements=buffer)) 79 | sliden += 1 80 | 81 | return slides 82 | -------------------------------------------------------------------------------- /present/slide.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import re 5 | import shutil 6 | from dataclasses import dataclass 7 | 8 | from pyfiglet import Figlet 9 | 10 | from .effects import COLORS, EFFECTS 11 | 12 | 13 | @dataclass 14 | class Heading(object): 15 | type: str = "heading" 16 | obj: dict = None 17 | fg: int = 0 18 | attr: int = 2 # Screen.A_NORMAL 19 | normal: int = 2 # Screen.A_NORMAL 20 | bg: int = 7 21 | 22 | @property 23 | def size(self): 24 | if self.obj["level"] == 1: 25 | f = Figlet() 26 | text = self.obj["children"][0]["text"] 27 | return len(f.renderText(text).splitlines()) 28 | elif self.obj["level"] == 2: 29 | return 2 30 | else: 31 | return 1 32 | 33 | def render(self): 34 | text = self.obj["children"][0]["text"] 35 | 36 | if self.obj["level"] == 1: 37 | f = Figlet() 38 | return f.renderText(text) 39 | elif self.obj["level"] == 2: 40 | return "\n".join([text, "-" * len(text)]) 41 | else: 42 | return text 43 | 44 | 45 | @dataclass 46 | class List(object): 47 | type: str = "list" 48 | obj: dict = None 49 | fg: int = 0 50 | attr: int = 2 # Screen.A_NORMAL 51 | normal: int = 2 # Screen.A_NORMAL 52 | bg: int = 7 53 | 54 | def walk(self, obj, text=None, level=0): 55 | if text is None: 56 | text = [] 57 | 58 | for child in obj.get("children", []): 59 | if child.get("text") is not None: 60 | text.append((" " * 2 * level) + "• " + child["text"]) 61 | 62 | if "children" in obj: 63 | self.walk(child, text=text, level=level + 1) 64 | 65 | return text 66 | 67 | @property 68 | def size(self): 69 | return len(self.walk(self.obj)) 70 | 71 | def render(self): 72 | return "\n".join(self.walk(self.obj)) 73 | 74 | 75 | @dataclass 76 | class BlockCode(object): 77 | type: str = "code" 78 | obj: dict = None 79 | fg: int = 0 80 | attr: int = 2 # Screen.A_NORMAL 81 | normal: int = 2 # Screen.A_NORMAL 82 | bg: int = 7 83 | 84 | @staticmethod 85 | def pad(s, fill=" "): 86 | lines = s.splitlines() 87 | max_len = max(len(l) for l in lines) 88 | top = bottom = " " * (max_len + 2) 89 | 90 | lines = [l.ljust(max_len + 1, fill) for l in lines] 91 | lines = [" " + l for l in lines] 92 | lines.insert(0, top) 93 | lines.append(bottom) 94 | 95 | return "\n".join(lines) 96 | 97 | @property 98 | def size(self): 99 | return len(self.obj["text"].splitlines()) 100 | 101 | def render(self): 102 | return self.pad(self.obj["text"]) 103 | 104 | 105 | @dataclass 106 | class Codio(object): 107 | type: str = "codio" 108 | obj: dict = None 109 | fg: int = 0 110 | attr: int = 2 # Screen.A_NORMAL 111 | normal: int = 2 # Screen.A_NORMAL 112 | bg: int = 7 113 | 114 | @property 115 | def speed(self): 116 | _speed = self.obj["speed"] 117 | 118 | if _speed < 1: 119 | warnings.warn("Codio speed < 1, setting it to 1") 120 | _speed = 1 121 | elif _speed > 10: 122 | warnings.warn("Codio speed > 10, setting it to 10") 123 | _speed = 10 124 | 125 | return 11 - _speed 126 | 127 | @property 128 | def width(self): 129 | _width = 0 130 | _terminal_width = int(shutil.get_terminal_size()[0] / 4) 131 | 132 | for l in self.obj["lines"]: 133 | prompt = l.get("prompt", "") 134 | inp = l.get("in", "") 135 | out = l.get("out", "") 136 | 137 | if l.get("progress") is not None and l["progress"]: 138 | _magic_width = _terminal_width 139 | else: 140 | _magic_width = 0 141 | 142 | _width = max( 143 | _width, 144 | _magic_width, 145 | len(prompt), 146 | len(inp) + inp.count(" "), 147 | len(out) + out.count(" "), 148 | ) 149 | 150 | return _width + 4 151 | 152 | @property 153 | def size(self): 154 | lines = len(self.obj["lines"]) 155 | 156 | for l in self.obj["lines"]: 157 | inp = l.get("in", "") 158 | out = l.get("out", "") 159 | if inp and out: 160 | lines += 1 161 | 162 | return lines + 2 163 | 164 | def render(self): 165 | _code = [] 166 | _width = self.width 167 | 168 | for line in self.obj["lines"]: 169 | _c = {} 170 | 171 | # if there is a progress bar, don't display prompt or add style 172 | if line.get("progress") is not None and line["progress"]: 173 | progress_char = line.get("progressChar", "█") 174 | _c["prompt"] = "" 175 | _c["in"] = progress_char * int(0.6 * _width) 176 | _c["out"] = "" 177 | else: 178 | prompt = line.get("prompt", "") 179 | inp = line.get("in", "") 180 | out = line.get("out", "") 181 | 182 | if not (prompt or inp or out): 183 | continue 184 | 185 | # if only prompt is present, print it all at once 186 | if prompt and not inp and not out: 187 | out = prompt 188 | prompt = "" 189 | 190 | _c["prompt"] = prompt 191 | _c["in"] = inp 192 | _c["out"] = out 193 | 194 | _c["color"] = line.get("color") 195 | _c["bold"] = line.get("bold") 196 | _c["underline"] = line.get("underline") 197 | 198 | _code.append(_c) 199 | 200 | return _code 201 | 202 | 203 | @dataclass(init=False) 204 | class Image(object): 205 | def __init__( 206 | self, 207 | type: str = "image", 208 | obj: dict = None, 209 | fg: int = 0, 210 | attr: int = 2, 211 | normal: int = 2, 212 | bg: int = 7, 213 | ): 214 | self.type = type 215 | self.obj = obj 216 | self.fg = fg 217 | self.attr = attr 218 | self.normal = normal 219 | self.bg = bg 220 | if not os.path.exists(self.obj["src"]): 221 | raise FileNotFoundError(f"{self.obj['src']} does not exist") 222 | 223 | @property 224 | def size(self): 225 | # TODO: Support small, medium, large image sizes 226 | return int(shutil.get_terminal_size()[1] / 2) 227 | 228 | def render(self): 229 | raise NotImplementedError 230 | 231 | 232 | @dataclass 233 | class BlockHtml(object): 234 | type: str = "html" 235 | obj: dict = None 236 | fg: int = 0 237 | attr: int = 2 # Screen.A_NORMAL 238 | normal: int = 2 # Screen.A_NORMAL 239 | bg: int = 7 240 | 241 | @property 242 | def size(self): 243 | raise NotImplementedError 244 | 245 | @property 246 | def style(self): 247 | _style = re.findall(r"((\w+)=(\w+))", self.obj["text"]) 248 | return {s[1]: s[2] for s in _style} 249 | 250 | def render(self): 251 | raise NotImplementedError 252 | 253 | 254 | @dataclass 255 | class Text(object): 256 | type: str = "text" 257 | obj: dict = None 258 | fg: int = 0 259 | attr: int = 2 # Screen.A_NORMAL 260 | normal: int = 2 # Screen.A_NORMAL 261 | bg: int = 7 262 | 263 | @property 264 | def size(self): 265 | return 1 266 | 267 | def render(self): 268 | return self.obj["text"] 269 | 270 | 271 | @dataclass 272 | class Codespan(object): 273 | type: str = "codespan" 274 | obj: dict = None 275 | fg: int = 0 276 | attr: int = 3 # Screen.A_REVERSE 277 | normal: int = 2 # Screen.A_NORMAL 278 | bg: int = 7 279 | 280 | @property 281 | def size(self): 282 | raise NotImplementedError 283 | 284 | def render(self): 285 | return ( 286 | f"${{{self.fg},{self.attr},{self.bg}}}" 287 | + self.obj["text"] 288 | + f"${{{self.fg},{self.normal},{self.bg}}}" 289 | ) 290 | 291 | 292 | @dataclass 293 | class Strong(object): 294 | type: str = "strong" 295 | obj: dict = None 296 | fg: int = 0 297 | attr: int = 1 # Screen.A_BOLD 298 | normal: int = 2 # Screen.A_NORMAL 299 | bg: int = 7 300 | 301 | @property 302 | def size(self): 303 | raise NotImplementedError 304 | 305 | def render(self): 306 | return ( 307 | f"${{{self.fg},{self.attr},{self.bg}}}" 308 | + self.obj["children"][0]["text"] 309 | + f"${{{self.fg},{self.normal},{self.bg}}}" 310 | ) 311 | 312 | 313 | @dataclass 314 | class Emphasis(object): 315 | type: str = "emphasis" 316 | obj: dict = None 317 | fg: int = 0 318 | attr: int = 2 # Screen.A_NORMAL 319 | normal: int = 2 # Screen.A_NORMAL 320 | bg: int = 7 321 | 322 | @property 323 | def size(self): 324 | raise NotImplementedError 325 | 326 | def render(self): 327 | # TODO: add italic support 328 | return self.obj["children"][0]["text"] 329 | 330 | 331 | @dataclass 332 | class Link(object): 333 | type: str = "link" 334 | obj: dict = None 335 | fg: int = 0 336 | attr: int = 2 # Screen.A_NORMAL 337 | normal: int = 2 # Screen.A_NORMAL 338 | bg: int = 7 339 | 340 | @property 341 | def size(self): 342 | raise NotImplementedError 343 | 344 | def render(self): 345 | return f"{self.obj['children'][0]['text']} ({self.obj['link']})" 346 | 347 | 348 | @dataclass 349 | class Paragraph(object): 350 | type: str = "paragraph" 351 | obj: dict = None 352 | fg: int = 0 353 | attr: int = 2 # Screen.A_NORMAL 354 | normal: int = 2 # Screen.A_NORMAL 355 | bg: int = 7 356 | 357 | @property 358 | def size(self): 359 | # TODO: paragraph size should be sum of all element sizes in it 360 | return 1 361 | 362 | def render(self): 363 | text = "" 364 | 365 | for child in self.obj["children"]: 366 | element_name = child["type"].title().replace("_", "") 367 | Element = eval(element_name) 368 | e = Element(obj=child, fg=self.fg, bg=self.bg) 369 | text += e.render() 370 | 371 | return text 372 | 373 | 374 | @dataclass 375 | class BlockQuote(object): 376 | type: str = "quote" 377 | obj: dict = None 378 | fg: int = 0 379 | attr: int = 2 # Screen.A_NORMAL 380 | normal: int = 2 # Screen.A_NORMAL 381 | bg: int = 7 382 | 383 | @property 384 | def size(self): 385 | return len(self.obj["children"]) 386 | 387 | def render(self): 388 | text = [] 389 | 390 | for child in self.obj["children"]: 391 | p = Paragraph(obj=child, fg=self.fg, bg=self.bg) 392 | for t in p.render().split("\n"): 393 | text.append(f"▌ {t}") 394 | 395 | return "\n".join(text) 396 | 397 | 398 | class Slide(object): 399 | def __init__(self, elements=None): 400 | self.elements = elements 401 | self.has_style = False 402 | self.has_effect = False 403 | self.has_image = False 404 | self.has_code = False 405 | self.has_codio = False 406 | self.style = {} 407 | self.effect = None 408 | self.fg_color = 0 409 | self.bg_color = 7 410 | 411 | _elements = [] 412 | 413 | for e in self.elements: 414 | # TODO: raise warning if multiple styles 415 | if e.type == "html": 416 | if e.style: 417 | self.has_style = True 418 | self.style = e.style 419 | # remove html comments 420 | continue 421 | 422 | if e.type == "image": 423 | self.has_image = True 424 | 425 | if e.type == "code": 426 | self.has_code = True 427 | 428 | if e.type == "codio": 429 | self.has_codio = True 430 | 431 | _elements.append(e) 432 | 433 | self.elements = _elements 434 | 435 | # TODO: support everything! 436 | if self.style.get("effect") is not None: 437 | if self.style["effect"] not in EFFECTS: 438 | raise ValueError(f"Effect {self.style['effect']} is not supported") 439 | self.has_effect = True 440 | self.effect = self.style["effect"] 441 | 442 | if self.style.get("fg") is not None: 443 | try: 444 | self.fg_color = COLORS[self.style["fg"]] 445 | except KeyError: 446 | raise ValueError(f"Color {self.style['fg']} is not supported") 447 | 448 | if self.style.get("bg") is not None: 449 | try: 450 | self.bg_color = COLORS[self.style["bg"]] 451 | except KeyError: 452 | raise ValueError(f"Color {self.style['bg']} is not supported") 453 | 454 | if self.has_effect and ( 455 | self.style.get("fg") is not None or self.style.get("bg") is not None 456 | ): 457 | raise ValueError("Effects and colors on the same slide are not supported") 458 | 459 | if self.has_effect and self.has_code: 460 | raise ValueError("Effects and code on the same slide are not supported") 461 | 462 | if self.has_effect: 463 | self.fg_color, self.bg_color = 7, 0 464 | 465 | # apply fg and bg color to all elements 466 | for e in self.elements: 467 | e.fg = self.fg_color 468 | e.bg = self.bg_color 469 | 470 | def __repr__(self): 471 | return f"" 472 | -------------------------------------------------------------------------------- /present/slideshow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import time 4 | 5 | from asciimatics.scene import Scene 6 | from asciimatics.screen import Screen 7 | from asciimatics.effects import Print 8 | from asciimatics.event import KeyboardEvent 9 | from asciimatics.exceptions import ResizeScreenError, StopApplication 10 | 11 | from .effects import ( 12 | _reset, 13 | _base, 14 | _code, 15 | _codio, 16 | _image, 17 | _fireworks, 18 | _explosions, 19 | _stars, 20 | _matrix, 21 | _plasma, 22 | Codio, 23 | ) 24 | 25 | 26 | class Slide(Scene): 27 | def __init__(self, show, effects, fg_color, bg_color): 28 | self.show = show 29 | self.fg_color = fg_color 30 | self.bg_color = bg_color 31 | super(Slide, self).__init__(effects) 32 | 33 | def _reset(self): 34 | for effect in self._effects: 35 | if isinstance(effect, Print) and isinstance(effect._renderer, Codio): 36 | effect._renderer._reset() 37 | 38 | def process_event(self, event): 39 | if super(Slide, self).process_event(event) is None: 40 | return 41 | 42 | if isinstance(event, KeyboardEvent): 43 | c = event.key_code 44 | if c == ord("r"): 45 | self._reset() 46 | elif c in (ord("b"), Screen.KEY_LEFT, Screen.KEY_PAGE_UP): 47 | self.show.current_slide -= 1 48 | 49 | try: 50 | self.show.screen.set_scenes( 51 | [self.show.slides[self.show.current_slide]] 52 | ) 53 | self.show.screen.clear_buffer( 54 | self.show.slides[self.show.current_slide].fg_color, 55 | 0, 56 | self.show.slides[self.show.current_slide].bg_color, 57 | ) 58 | except IndexError: 59 | pass 60 | elif c in (ord(" "), ord("n"), Screen.KEY_RIGHT, Screen.KEY_PAGE_DOWN): 61 | self.show.current_slide += 1 62 | 63 | try: 64 | self.show.screen.set_scenes( 65 | [self.show.slides[self.show.current_slide]] 66 | ) 67 | self.show.screen.clear_buffer( 68 | self.show.slides[self.show.current_slide].fg_color, 69 | 0, 70 | self.show.slides[self.show.current_slide].bg_color, 71 | ) 72 | except IndexError: 73 | pass 74 | else: 75 | return event 76 | else: 77 | return event 78 | 79 | 80 | class Slideshow(object): 81 | def __init__(self, slides): 82 | self.slides = slides 83 | self.current_slide = 0 84 | self.screen = None 85 | self.reset = None 86 | 87 | super(Slideshow, self).__init__() 88 | 89 | def __enter__(self): 90 | self.screen = Screen.open() 91 | return self 92 | 93 | def __exit__(self, type, value, traceback): 94 | self.screen.close() 95 | 96 | def get_effects(self, slide): 97 | effects = [] 98 | transparent = True 99 | elements = slide.elements 100 | fg_color, bg_color = slide.fg_color, slide.bg_color 101 | 102 | if ( 103 | len(elements) == 1 104 | and not slide.has_image 105 | and not slide.has_code 106 | and not slide.has_codio 107 | ): 108 | row = int(self.screen.height / 2) - elements[0].size 109 | else: 110 | row = int(self.screen.height * 0.2) 111 | 112 | pad = 2 113 | for e in elements: 114 | if e.type == "code": 115 | effects.extend(_code(self.screen, e, row)) 116 | pad = 4 117 | elif e.type == "codio": 118 | effects.extend(_codio(self.screen, e, row)) 119 | pad = 4 120 | elif e.type == "image": 121 | effects.extend(_image(self.screen, e, row, bg_color)) 122 | pad = 2 123 | else: 124 | effects.extend(_base(self.screen, e, row, fg_color, bg_color)) 125 | pad = 2 126 | 127 | row += e.size + pad 128 | 129 | if slide.has_style and slide.effect is not None: 130 | _effect = eval(f"_{slide.effect}")(self.screen) 131 | effects.extend(list(_effect)) 132 | 133 | return effects 134 | 135 | def play( 136 | self, 137 | stop_on_resize=False, 138 | unhandled_input=None, 139 | start_scene=None, 140 | repeat=True, 141 | allow_int=False, 142 | ): 143 | self.reset = [Slide(self, _reset(self.screen), 7, 0)] 144 | 145 | self.slides = [ 146 | Slide(self, self.get_effects(slide), slide.fg_color, slide.bg_color) 147 | for slide in self.slides 148 | ] 149 | 150 | # Initialise the Screen for animation. 151 | self.screen.set_scenes( 152 | self.slides, unhandled_input=unhandled_input, start_scene=start_scene 153 | ) 154 | self.screen.clear_buffer(self.slides[0].fg_color, 0, self.slides[0].bg_color) 155 | 156 | # Mainline loop for animations 157 | try: 158 | while True: 159 | if self.current_slide == len(self.slides): 160 | self.screen.set_scenes(self.reset) 161 | 162 | while True: 163 | a = time.time() 164 | self.screen.draw_next_frame(repeat=repeat) 165 | b = time.time() 166 | if b - a < 0.05: 167 | # Just in case time has jumped (e.g. time change), ensure we only delay for 0.05s 168 | pause = min(0.05, a + 0.05 - b) 169 | if allow_int: 170 | self.screen.wait_for_input(pause) 171 | else: 172 | time.sleep(pause) 173 | 174 | event = self.screen.get_event() 175 | if isinstance(event, KeyboardEvent): 176 | if event.key_code == ord("r"): 177 | self.current_slide = 0 178 | self.screen.set_scenes( 179 | [self.slides[self.current_slide]] 180 | ) 181 | self.screen.clear_buffer( 182 | self.slides[self.current_slide].fg_color, 183 | 0, 184 | self.slides[self.current_slide].bg_color, 185 | ) 186 | break 187 | else: 188 | raise StopApplication("Stop slideshow") 189 | 190 | a = time.time() 191 | self.screen.draw_next_frame(repeat=repeat) 192 | if self.screen.has_resized(): 193 | if stop_on_resize: 194 | self.screen._scenes[self.screen._scene_index].exit() 195 | raise ResizeScreenError( 196 | "Screen resized", 197 | self.screen._scenes[self.screen._scene_index], 198 | ) 199 | b = time.time() 200 | if b - a < 0.05: 201 | # Just in case time has jumped (e.g. time change), ensure we only delay for 0.05s 202 | pause = min(0.05, a + 0.05 - b) 203 | if allow_int: 204 | self.screen.wait_for_input(pause) 205 | else: 206 | time.sleep(pause) 207 | except StopApplication: 208 | # Time to stop - just exit the function. 209 | self.screen.clear() 210 | return 211 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 40.6.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | from setuptools import find_packages 5 | 6 | 7 | here = os.path.abspath(os.path.dirname(__file__)) 8 | about = {} 9 | with open(os.path.join(here, "present", "__version__.py"), "r") as f: 10 | exec(f.read(), about) 11 | 12 | with open("README.md", "r") as f: 13 | readme = f.read() 14 | 15 | requires = [ 16 | "asciimatics>=1.11.0", 17 | "Click>=7.0", 18 | "mistune>=2.0.0a4", 19 | "pyfiglet>=0.8.post1", 20 | "PyYAML>=5.3.1", 21 | ] 22 | dev_requires = ["black>=20.8b1", "Sphinx>=2.2.1"] 23 | dev_requires = dev_requires + requires 24 | 25 | 26 | def setup_package(): 27 | metadata = dict( 28 | name=about["__title__"], 29 | version=about["__version__"], 30 | description=about["__description__"], 31 | long_description=readme, 32 | long_description_content_type="text/markdown", 33 | license=about["__license__"], 34 | classifiers=[ 35 | # Trove classifiers 36 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 37 | "Environment :: Console", 38 | "License :: OSI Approved :: Apache Software License", 39 | "Programming Language :: Python", 40 | "Programming Language :: Python :: 3.7", 41 | "Programming Language :: Python :: 3.8", 42 | "Programming Language :: Python :: 3.9", 43 | "Programming Language :: Python :: 3 :: Only", 44 | ], 45 | url=about["__url__"], 46 | project_urls={ 47 | "Documentation": "https://present.readthedocs.io", 48 | "Source": "https://github.com/vinayak-mehta/present", 49 | "Changelog": "https://github.com/vinayak-mehta/present/blob/master/HISTORY.md", 50 | }, 51 | author=about["__author__"], 52 | author_email=about["__author_email__"], 53 | packages=find_packages(exclude=("tests",)), 54 | entry_points={"console_scripts": ["present = present.cli:cli"]}, 55 | install_requires=requires, 56 | extras_require={"dev": dev_requires}, 57 | python_requires=">=3.7", 58 | ) 59 | 60 | try: 61 | from setuptools import setup 62 | except ImportError: 63 | from distutils.core import setup 64 | 65 | setup(**metadata) 66 | 67 | 68 | if __name__ == "__main__": 69 | setup_package() 70 | --------------------------------------------------------------------------------