pypresent 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. pypresent-0.1.0/.gitignore +221 -0
  2. pypresent-0.1.0/CHANGELOG.md +81 -0
  3. pypresent-0.1.0/LICENSE +21 -0
  4. pypresent-0.1.0/PKG-INFO +235 -0
  5. pypresent-0.1.0/README.md +188 -0
  6. pypresent-0.1.0/demos/README.md +69 -0
  7. pypresent-0.1.0/docs/README.md +673 -0
  8. pypresent-0.1.0/docs/img/quoted-figure.png +0 -0
  9. pypresent-0.1.0/docs/img/quoted-output.png +0 -0
  10. pypresent-0.1.0/pyproject.toml +88 -0
  11. pypresent-0.1.0/src/pypresent/__init__.py +76 -0
  12. pypresent-0.1.0/src/pypresent/__main__.py +3 -0
  13. pypresent-0.1.0/src/pypresent/assets/deck.css +157 -0
  14. pypresent-0.1.0/src/pypresent/assets/deck.js +69 -0
  15. pypresent-0.1.0/src/pypresent/blocks.py +192 -0
  16. pypresent-0.1.0/src/pypresent/cli.py +175 -0
  17. pypresent-0.1.0/src/pypresent/console.py +27 -0
  18. pypresent-0.1.0/src/pypresent/context.py +41 -0
  19. pypresent-0.1.0/src/pypresent/deck.py +402 -0
  20. pypresent-0.1.0/src/pypresent/markup.py +69 -0
  21. pypresent-0.1.0/src/pypresent/model.py +45 -0
  22. pypresent-0.1.0/src/pypresent/nbio.py +193 -0
  23. pypresent-0.1.0/src/pypresent/presentation.py +495 -0
  24. pypresent-0.1.0/src/pypresent/render/__init__.py +6 -0
  25. pypresent-0.1.0/src/pypresent/render/html.py +201 -0
  26. pypresent-0.1.0/src/pypresent/render/markdown.py +117 -0
  27. pypresent-0.1.0/src/pypresent/theme.py +270 -0
  28. pypresent-0.1.0/src/pypresent/themes/dark.toml +23 -0
  29. pypresent-0.1.0/src/pypresent/themes/office.toml +23 -0
  30. pypresent-0.1.0/src/pypresent/themes/slate.toml +29 -0
  31. pypresent-0.1.0/src/pypresent/themes/warm.toml +47 -0
  32. pypresent-0.1.0/tests/conftest.py +100 -0
  33. pypresent-0.1.0/tests/test_blocks.py +152 -0
  34. pypresent-0.1.0/tests/test_cli.py +181 -0
  35. pypresent-0.1.0/tests/test_console.py +84 -0
  36. pypresent-0.1.0/tests/test_markdown_source.py +113 -0
  37. pypresent-0.1.0/tests/test_markup.py +50 -0
  38. pypresent-0.1.0/tests/test_notebook_source.py +143 -0
  39. pypresent-0.1.0/tests/test_presentation.py +226 -0
  40. pypresent-0.1.0/tests/test_readme.py +93 -0
  41. pypresent-0.1.0/tests/test_render.py +137 -0
  42. pypresent-0.1.0/tests/test_theme.py +111 -0
@@ -0,0 +1,221 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
219
+
220
+ # pypresent: built decks, all rebuilt by demos/build.sh
221
+ demos/out/
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
4
+ versions are [semantic](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] — 2026-11-07
9
+
10
+ The first release. It began as a single `present.py` that built one lecture's
11
+ deck, and is now a package.
12
+
13
+ ### Added
14
+
15
+ - **Markdown decks.** A `.md` file is a source in its own right: headings and
16
+ `---` lines cut it into slides, and a `key: value` front matter block declares
17
+ the deck the way a notebook's first cell does.
18
+ - **Markdown output.** `--format md` writes the same slides as a markdown
19
+ document — a readable handout that `pypresent` can read straight back in.
20
+ Pictures are data URIs by default, or written out beside the file with
21
+ `render_markdown(assets=…)`.
22
+ - **Themes.** Every colour, size and font is a named token; the stylesheet is
23
+ written entirely in terms of them. A theme is a name, a `.toml`/`.json`/`.yaml`
24
+ file, a dict, or a `Theme` — with `base` to build on a built-in. Four ship:
25
+ `warm`, `office`, `dark`, `slate`. `pypresent themes NAME` prints one as a file
26
+ you can edit.
27
+ - **`pypresent check`** as a command of its own, so drift can be found without
28
+ rendering.
29
+ - **`notes()`**, and `notes=` on a slide: speaker notes that stay in the deck and
30
+ never reach the slide.
31
+ - **`kernel="auto"`** (the default): a notebook is executed by the very
32
+ interpreter `pypresent` is installed in, so a fresh virtualenv and a CI runner
33
+ both work with no kernelspec registered anywhere.
34
+ - Images beyond PNG — jpg, gif, webp, svg — embedded by their real type.
35
+ - A test suite, a demo folder, and CI on Linux, macOS and Windows.
36
+
37
+ ### Removed
38
+
39
+ - **`audit`.** It opened the built deck in headless Chrome to report which
40
+ slides the renderer had shrunk to fit. That is one subprocess, one browser to
41
+ have installed and one injected probe script, to say something you see the
42
+ moment you open the deck. The shrinking itself is unchanged — an over-full
43
+ slide still scales rather than overflowing — and with it went `fit_floor`,
44
+ `sparse`, `audit_size` and `chrome` on a `Presentation`.
45
+
46
+ ### Changed
47
+
48
+ - `present.py` is now the `pypresent` package, and the tool is `pypresent`
49
+ rather than `python present.py`.
50
+ - `lecture=` is now `source=`, since the notebook being quoted need not be a
51
+ lecture. `lecture=` still works, and a deck whose declaration was stored under
52
+ the old name still loads.
53
+ - The command's first positional is the deck, and it may be a `.md`.
54
+ - **`build` runs the source notebook as well, by default.** The deck quotes what
55
+ the source printed, so a build that does not re-run it can put new code beside
56
+ an old number; that was opt-in behind `--source` and is now what a build is.
57
+ The two runs are taken away rather than added: `--skip-source-run` and
58
+ `--skip-slides-run` replace `--source` and `--no-run`, and passing both
59
+ executes nothing. In Python, `build(run=…, source=…)` is now
60
+ `build(run_source=…, run_slides=…)`.
61
+ - `export --mode slide --format md` now uses this package's own renderer rather
62
+ than nbconvert, so it writes the slides rather than the notebook.
63
+ - `-o` on the command line is relative to the shell's directory rather than the
64
+ deck's.
65
+
66
+ ### Fixed
67
+
68
+ - `trim={'head': n, 'tail': n}` on a block that already fits no longer truncates
69
+ it and drops the tail.
70
+ - The cell that declares the presentation is no longer rendered as a slide of its
71
+ own source, and `check` no longer complains that it declares no slide.
72
+ - **A non-UTF-8 terminal no longer breaks a build.** A deck carries arrows, a
73
+ middle dot and often Hebrew, and a redirected stdout under a locale that
74
+ cannot encode them - a pipe on a Windows runner, a cron job with `LC_ALL=C` -
75
+ made `pypresent` die printing its own progress line. Messages now fall back to
76
+ a plain-text equivalent (`·` to `-`, `…` to `...`); the deck file itself was
77
+ always written as UTF-8 and is unchanged. CI runs the suite under `LC_ALL=C`
78
+ so this is caught in seconds rather than in the Windows job.
79
+
80
+ [Unreleased]: https://github.com/Barakkol33/pypresent/compare/v0.1.0...HEAD
81
+ [0.1.0]: https://github.com/Barakkol33/pypresent/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Barak Kolnik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,235 @@
1
+ Metadata-Version: 2.5
2
+ Name: pypresent
3
+ Version: 0.1.0
4
+ Summary: Slides from a Jupyter notebook, quoting its code and output, as one self-contained HTML file.
5
+ Project-URL: Homepage, https://github.com/Barakkol33/pypresent
6
+ Project-URL: Documentation, https://github.com/Barakkol33/pypresent/blob/main/docs/README.md
7
+ Project-URL: Source, https://github.com/Barakkol33/pypresent
8
+ Project-URL: Issues, https://github.com/Barakkol33/pypresent/issues
9
+ Project-URL: Changelog, https://github.com/Barakkol33/pypresent/blob/main/CHANGELOG.md
10
+ Author-email: Barak Kolnik <barakkol@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: html,jupyter,markdown,notebook,presentation,reveal,slides
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Framework :: Jupyter
17
+ Classifier: Intended Audience :: Education
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Multimedia :: Graphics :: Presentation
25
+ Classifier: Topic :: Text Processing :: Markup :: HTML
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.2; extra == 'dev'
31
+ Requires-Dist: ipykernel>=6.0; extra == 'dev'
32
+ Requires-Dist: nbclient>=0.9; extra == 'dev'
33
+ Requires-Dist: nbconvert>=7.0; extra == 'dev'
34
+ Requires-Dist: nbformat>=5.9; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
36
+ Requires-Dist: pytest>=8.0; extra == 'dev'
37
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
38
+ Requires-Dist: ruff>=0.6; extra == 'dev'
39
+ Provides-Extra: jupyter
40
+ Requires-Dist: ipykernel>=6.0; extra == 'jupyter'
41
+ Requires-Dist: nbclient>=0.9; extra == 'jupyter'
42
+ Requires-Dist: nbconvert>=7.0; extra == 'jupyter'
43
+ Requires-Dist: nbformat>=5.9; extra == 'jupyter'
44
+ Provides-Extra: yaml
45
+ Requires-Dist: pyyaml>=6.0; extra == 'yaml'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # pypresent
49
+
50
+ **The notebook is the slides.** `pypresent` turns a Jupyter notebook into one
51
+ self-contained HTML deck: no server, no build folder, no `_files/` directory,
52
+ nothing to upload. One file you can mail, open from a memory stick, or commit
53
+ next to the notebook it came from.
54
+
55
+ And the deck never pastes the code — it quotes the lecture, at build time.
56
+
57
+ [![CI](https://github.com/Barakkol33/pypresent/actions/workflows/ci.yml/badge.svg)](https://github.com/Barakkol33/pypresent/actions/workflows/ci.yml)
58
+ [![PyPI](https://img.shields.io/pypi/v/pypresent.svg)](https://pypi.org/project/pypresent/)
59
+ [![Python](https://img.shields.io/pypi/pyversions/pypresent.svg)](https://pypi.org/project/pypresent/)
60
+ [![License](https://img.shields.io/pypi/l/pypresent.svg)](LICENSE)
61
+
62
+ ```bash
63
+ pip install pypresent # rendering only - no Jupyter needed
64
+ pip install "pypresent[jupyter]" # and executing the notebooks
65
+ ```
66
+
67
+ ## The point of it
68
+
69
+ A lecture notebook and its deck usually drift apart: the code is pasted onto a
70
+ slide, the number beside it was true three runs ago, and the chart is a PNG
71
+ somebody exported in March.
72
+
73
+ `pypresent` never pastes. A cell in the lecture is named with a comment, and is
74
+ otherwise an ordinary cell that knows nothing about any deck — here `tokenize`
75
+ came from a cell above it:
76
+
77
+ ```python
78
+ # slide: counts
79
+ from collections import Counter
80
+
81
+ CORPUS = 'the cat sat on the mat and the cat sat again'
82
+ counts = Counter(tokenize(CORPUS))
83
+ print(counts.most_common(3))
84
+ print(f'{len(counts)} distinct tokens')
85
+ ```
86
+
87
+ Run the lecture and it prints:
88
+
89
+ ```
90
+ [('cat', 2), ('sat', 2), ('mat', 1)]
91
+ 6 distinct tokens
92
+ ```
93
+
94
+ The deck then quotes that cell **by name**, and copies nothing:
95
+
96
+ ```python
97
+ slide('''
98
+ ## And what it printed
99
+
100
+ - `result()` is the cell's own output, not a number typed twice
101
+ - re-run the lecture and the slide changes with it
102
+ ''',
103
+ code('counts', drop=['from collections']),
104
+ result('counts'),
105
+ layout='split',
106
+ )
107
+ ```
108
+
109
+ Then:
110
+
111
+ ```bash
112
+ pypresent build # run the lecture, run the deck, write one html file
113
+ ```
114
+
115
+ which is this slide — the listing is the code that ran, and the numbers under it
116
+ are the ones it printed:
117
+
118
+ ![A slide: two bullets on the left; on the right the quoted listing, and under it a lighter box holding the output it produced](docs/img/quoted-output.png)
119
+
120
+ `code()` is the listing, `result()` is what it printed, and `figure()` is what it
121
+ drew — a chart is code output the same way a number is, read out of the lecture's
122
+ stored output at render time, so there is no PNG beside the deck to go stale:
123
+
124
+ ![A slide: three bullets on the left; on the right a framed bar chart of token lengths, drawn by the lecture](docs/img/quoted-figure.png)
125
+
126
+ All three are resolved when the deck is built, so what a slide shows is what
127
+ actually ran — and a name that stops matching is reported by the build rather
128
+ than going quietly stale.
129
+
130
+ Both slides above are [`demos/05-notebook-slides.ipynb`](demos/05-notebook-slides.ipynb)
131
+ as it stands, rendered by `./demos/build.sh` and screenshotted by
132
+ [`demos/screenshot.sh`](demos/screenshot.sh).
133
+
134
+ ## What a deck declares
135
+
136
+ The slide notebook says everything about itself in its first cell, so the
137
+ command line needs nothing but the file:
138
+
139
+ ```python
140
+ from pypresent import Presentation, slide, code, result, figure
141
+
142
+ deck = Presentation(
143
+ slides='talk-slides.ipynb', # this notebook: nothing but slide() calls
144
+ source='talk.ipynb', # what code(), result() and figure() quote
145
+ output='out/talk.html',
146
+ title='A talk', lang='en', theme='office',
147
+ )
148
+ deck # the last line, so the cell stores it
149
+ ```
150
+
151
+ The declaration is kept as a cell output, the way a slide is. A flag still wins
152
+ over it: `pypresent build --theme dark -o /tmp/x.html`.
153
+
154
+ ## Themes
155
+
156
+ Every colour, size and font is a named token, and the stylesheet is written
157
+ entirely in terms of them — so restyling is changing values, never forking a
158
+ renderer.
159
+
160
+ ```python
161
+ Presentation(..., theme='dark') # a built-in
162
+ Presentation(..., theme={'base': 'office', 'accent': '#a8441f'})
163
+ Presentation(..., theme='themes/house-style.toml') # a file
164
+ Presentation(..., theme=Theme.named('slate').replace(bullet_size='4.6cqh'))
165
+ ```
166
+
167
+ Four built-ins ship with it — `warm`, `office`, `dark`, `slate`. To start from
168
+ one, print it as a file you can edit:
169
+
170
+ ```bash
171
+ pypresent themes # what there is
172
+ pypresent themes dark > mine.toml
173
+ pypresent build --theme mine.toml
174
+ ```
175
+
176
+ `css=` is still there for the one selector no token covers.
177
+ See [the theme reference](docs/README.md#themes) for every token.
178
+
179
+ ## Right to left
180
+
181
+ `lang='he'` implies `dir="rtl"`, which mirrors the whole layout — bullets, the
182
+ two-tone title rule, the cover band, the arrow keys — and switches to a
183
+ Hebrew-first font stack, while keeping code and output left to right, because
184
+ Python does not read right to left.
185
+
186
+ ## Commands
187
+
188
+ | | |
189
+ | --- | --- |
190
+ | `pypresent build` | run the source notebook, then the slide notebook, check, render |
191
+ | `pypresent build --skip-source-run` | leave the source notebook alone |
192
+ | `pypresent build --skip-slides-run` | check and render the stored outputs |
193
+ | `pypresent render` | render only, no kernel and no check |
194
+ | `pypresent check` | what has drifted, said and not corrected |
195
+ | `pypresent export --mode nb --format html` | the source notebook through nbconvert |
196
+ | `pypresent themes [name]` | the built-in themes |
197
+
198
+ ## It also takes a markdown file
199
+
200
+ A `.md` file works as a source, cut into slides by its headings and `---` lines,
201
+ with a front matter block declaring the deck. It quotes nothing — there is no
202
+ notebook to quote — so it is the lesser half of this, but it is there when a talk
203
+ has no code in it:
204
+
205
+ ```bash
206
+ pypresent render talk.md
207
+ ```
208
+
209
+ `--format md` writes slides back out the same way. See
210
+ [the docs](docs/README.md#writing-slides).
211
+
212
+ ## Documentation
213
+
214
+ **[The documentation is one page](docs/README.md)**, with everything on it:
215
+
216
+ - [Quickstart](docs/README.md#quickstart) — from nothing to a deck
217
+ - [Writing slides](docs/README.md#writing-slides) — every block, and what it is for
218
+ - [Themes](docs/README.md#themes) — every token, and how to ship your own
219
+ - [The command line](docs/README.md#the-command-line)
220
+ - [The Python API](docs/README.md#the-python-api)
221
+
222
+ And [`demos/`](demos/) is the same thing as decks you can open — every block, the
223
+ four themes, a right-to-left deck, a hand-written theme, and a notebook deck that
224
+ quotes its lecture.
225
+
226
+ ## Why not reveal.js / Quarto / RISE
227
+
228
+ Those are all good, and all bigger. `pypresent` is for one case: a lecture
229
+ notebook that must stay the single source of the code, and a deck of it that
230
+ must be one file with no toolchain to reproduce. If you want speaker view,
231
+ fragments, plugins and a themeing ecosystem, use reveal.js.
232
+
233
+ ## License
234
+
235
+ MIT — see [LICENSE](LICENSE).