Set app title from deck name (#169)

pull/170/head
Josh Karpel 1 year ago committed by GitHub
parent f08ed7e7c7
commit 6321c01418
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -20,6 +20,7 @@ jobs:
env:
PLATFORM: ${{ matrix.platform }}
PYTHON_VERSION: ${{ matrix.python-version }}
PYTHONUTF8: 1 # https://peps.python.org/pep-0540/
steps:
- name: Check out repository
uses: actions/checkout@v3.2.0
@ -42,6 +43,8 @@ jobs:
run: poetry install --no-interaction --no-root
- name: Install package
run: poetry install --no-interaction
- name: Run pre-commit checks
run: poetry run pre-commit run --all-files --show-diff-on-failure --color=always
- name: Make sure we can build the package
run: poetry build -vvv
- name: Run tests

@ -17,6 +17,7 @@ repos:
- id: forbid-new-submodules
- id: mixed-line-ending
- id: trailing-whitespace
exclude_types: [svg]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0
hooks:
@ -29,7 +30,7 @@ repos:
rev: v2.1.2
hooks:
- id: pycln
args: [ --config=pyproject.toml ]
args: [--config=pyproject.toml]
- repo: https://github.com/PyCQA/isort
rev: v5.11.3
hooks:
@ -38,3 +39,15 @@ repos:
rev: 22.12.0
hooks:
- id: black
- repo: local
hooks:
- id: generate-screenshots
name: generate screenshots
entry: python docs/generate_screenshots.py
language: system
always_run: true
pass_filenames: false
ci:
skip:
- generate-screenshots

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 98 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

@ -2,6 +2,10 @@
## 0.4.3
### Added
- [#169](https://github.com/JoshKarpel/spiel/pull/169) The Textual application title and subtitle are now set dynamically from the Spiel deck name and slide title, respectively.
### Fixed
- [#168](https://github.com/JoshKarpel/spiel/pull/168) The correct type for the `suspend` optional argument to slide-level keybinding functions is now available as `spiel.SuspendType`.

@ -0,0 +1,64 @@
#!/usr/bin/env python
from collections.abc import Iterable
from datetime import datetime
from functools import partial
from io import StringIO
from pathlib import Path
from more_itertools import intersperse
from rich.console import Console
from textual.app import App
from textual.pilot import Pilot
from spiel.app import SpielApp
ROOT_DIR = Path(__file__).resolve().parent.parent
ASSETS_DIR = ROOT_DIR / "docs" / "assets"
DECK_FILE = ROOT_DIR / "spiel" / "demo" / "demo.py"
def take_reproducible_screenshot(app: App[object]) -> str:
"""
Textual's screenshot functions don't let you control the unique_id argument to console.export_svg,
so this little shim just reproduces the internals of Textual's methods with more control.
"""
width, height = app.size
console = Console(
width=width,
height=height,
file=StringIO(),
force_terminal=True,
color_system="truecolor",
record=True,
legacy_windows=False,
)
screen_render = app.screen._compositor.render(full=True)
console.print(screen_render)
return console.export_svg(title=app.title, unique_id="spieldocs")
async def auto_pilot(pilot: Pilot, name: str, keys: Iterable[str]) -> None:
await pilot.press(*intersperse("_", keys))
(ASSETS_DIR / name).with_suffix(".svg").write_text(take_reproducible_screenshot(pilot.app))
await pilot.app.action_quit()
def take_screenshot(name: str, size: tuple[int, int], keys: Iterable[str]) -> None:
SpielApp(
deck_path=DECK_FILE,
watch_path=DECK_FILE.parent,
show_messages=False,
fixed_time=datetime(year=2022, month=12, day=17, hour=15, minute=31, second=42),
).run(
headless=True,
auto_pilot=partial(auto_pilot, name=name, keys=keys),
size=size,
)
take_screenshot(name="demo", size=(130, 35), keys=())
take_screenshot(name="deck", size=(130, 35), keys=("d", "right", "down"))
take_screenshot(name="help", size=(110, 35), keys=("?",))

@ -4,3 +4,4 @@ Spiel is a framework for building and presenting [richly-styled](https://github.
![The first slide of the demo deck](./assets/demo.svg)
![The demo deck in "deck view"](./assets/deck.svg)
![The demo deck in "help view"](./assets/help.svg)

154
poetry.lock generated

@ -188,6 +188,18 @@ files = [
{file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
]
[[package]]
name = "cfgv"
version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
[[package]]
name = "charset-normalizer"
version = "2.1.1"
@ -312,6 +324,18 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1
[package.extras]
toml = ["tomli"]
[[package]]
name = "distlib"
version = "0.3.6"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
files = [
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
]
[[package]]
name = "docopt"
version = "0.6.2"
@ -522,6 +546,21 @@ pytz = ["pytz (>=2014.1)"]
redis = ["redis (>=3.0.0)"]
zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.7)"]
[[package]]
name = "identify"
version = "2.5.11"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "identify-2.5.11-py2.py3-none-any.whl", hash = "sha256:e7db36b772b188099616aaf2accbee122949d1c6a1bac4f38196720d6f9f06db"},
{file = "identify-2.5.11.tar.gz", hash = "sha256:14b7076b29c99b1b0b8b08e96d448c7b877a9b07683cd8cfda2ea06af85ffa1c"},
]
[package.extras]
license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.4"
@ -780,6 +819,18 @@ files = [
griffe = ">=0.24"
mkdocstrings = ">=0.19"
[[package]]
name = "more-itertools"
version = "9.0.0"
description = "More routines for operating on iterables, beyond itertools"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"},
{file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"},
]
[[package]]
name = "msgpack"
version = "1.0.4"
@ -1001,6 +1052,21 @@ files = [
{file = "nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68"},
]
[[package]]
name = "nodeenv"
version = "1.7.0"
description = "Node.js virtual environment builder"
category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
]
[package.dependencies]
setuptools = "*"
[[package]]
name = "packaging"
version = "22.0"
@ -1088,6 +1154,22 @@ files = [
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
[[package]]
name = "platformdirs"
version = "2.6.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-2.6.0-py3-none-any.whl", hash = "sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca"},
{file = "platformdirs-2.6.0.tar.gz", hash = "sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"},
]
[package.extras]
docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
[[package]]
name = "pluggy"
version = "1.0.0"
@ -1104,6 +1186,26 @@ files = [
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "2.20.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
{file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
toml = "*"
virtualenv = ">=20.0.8"
[[package]]
name = "pygments"
version = "2.13.0"
@ -1375,6 +1477,23 @@ pygments = ">=2.6.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
[[package]]
name = "setuptools"
version = "65.6.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},
{file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "six"
version = "1.16.0"
@ -1434,6 +1553,18 @@ rich = ">=12.6.0,<13.0.0"
[package.extras]
dev = ["aiohttp (>=3.8.1)", "click (>=8.1.2)", "msgpack (>=1.0.3)"]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
[[package]]
name = "tomli"
version = "2.0.1"
@ -1496,6 +1627,27 @@ brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
version = "20.17.1"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
{file = "virtualenv-20.17.1-py3-none-any.whl", hash = "sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4"},
{file = "virtualenv-20.17.1.tar.gz", hash = "sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"},
]
[package.dependencies]
distlib = ">=0.3.6,<1"
filelock = ">=3.4.1,<4"
platformdirs = ">=2.4,<3"
[package.extras]
docs = ["proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-argparse (>=0.3.2)", "sphinx-rtd-theme (>=1)", "towncrier (>=22.8)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "watchdog"
version = "2.2.0"
@ -1675,4 +1827,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<4"
content-hash = "686263b06fbf2b15ee67a7f9594df0939b2cf56baaac7ebe97ef493ab1191e3f"
content-hash = "82b2c99e068c980b13d4db79f82c997dce9d17a314207e1fcf3755a93ddbd764"

@ -36,8 +36,10 @@ typer = ">=0.6"
Pillow = ">=8"
textual = "==0.4.0"
watchfiles = ">=0.18"
more-itertools = ">=9"
[tool.poetry.group.dev.dependencies]
pre-commit = ">=2.20.0"
pytest = ">=7"
pytest-watch = ">=4"
pytest-cov = ">=3"
@ -67,7 +69,7 @@ all = true
[tool.pytest.ini_options]
addopts = ["--strict-markers", "--mypy", "-n", "auto"]
testpaths = ["tests", "spiel"]
testpaths = ["tests", "spiel", "docs"]
[tool.mypy]
pretty = false

@ -71,22 +71,33 @@ class SpielApp(App[None]):
Binding("i", "repl", "Switch to the REPL."),
Binding("p", "screenshot", "Take a screenshot."),
]
SCREENS = {"slide": SlideScreen(), "deck": DeckScreen(), "help": HelpScreen()}
deck = reactive(Deck(name="New Deck"))
current_slide_idx = reactive(0)
message = reactive(Text(""))
def __init__(self, deck_path: Path, watch_path: Path):
def __init__(
self,
deck_path: Path,
watch_path: Path,
show_messages: bool = True,
fixed_time: datetime.datetime | None = None,
):
super().__init__()
self.deck_path = deck_path
self.watch_path = watch_path
self.show_messages = show_messages
self.fixed_time = fixed_time
async def on_mount(self) -> None:
self.deck = load_deck(self.deck_path)
self.reloader = asyncio.create_task(self.reload())
await self.install_screen(SlideScreen(), name="slide")
await self.install_screen(DeckScreen(), name="deck")
await self.install_screen(HelpScreen(), name="help")
await self.push_screen("slide")
async def reload(self) -> None:
@ -119,6 +130,9 @@ class SpielApp(App[None]):
)
def set_message_temporarily(self, message: Text, delay: float) -> None:
if not self.show_messages:
return
self.message = message
def clear() -> None:
@ -143,8 +157,12 @@ class SpielApp(App[None]):
self.current_slide_idx - self.deck_grid_width, 0, len(self.deck) - 1
)
def watch_deck(self, new_deck: Deck) -> None:
self.title = new_deck.name
def watch_current_slide_idx(self, new_current_slide_idx: int) -> None:
self.query_one(SlideWidget).triggers = Triggers.new()
self.sub_title = self.deck[new_current_slide_idx].title
def action_trigger(self) -> None:
now = monotonic()
@ -189,7 +207,7 @@ class SpielApp(App[None]):
@property
def deck_grid_width(self) -> int:
return max(self.console.size.width // 35, 1)
return max(self.size.width // 35, 1)
def present(deck_path: Path | str, watch_path: Optional[Path | str] = None) -> None:

@ -21,11 +21,11 @@ from rich.style import Style
from rich.syntax import Syntax
from rich.text import Text
from spiel import Slide, SuspendType, Triggers, __version__, present
from spiel import Slide, SuspendType, Triggers, present
from spiel.deck import Deck
from spiel.renderables.image import Image
deck = Deck(name=f"Spiel Demo Deck (v{__version__})")
deck = Deck(name=f"Spiel Demo Deck")
SPIEL = "[Spiel](https://github.com/JoshKarpel/spiel)"
RICH = "[Rich](https://rich.readthedocs.io/)"
@ -51,10 +51,9 @@ def what() -> RenderableType:
Anything you can display with Rich, you can display with Spiel (plus some other things)!
Use your right `` and left `` arrows keys to go forwards and backwards through the deck.
Press `ctrl-c` to exit.
Press `?` at any time to see the help screen, which describes all of the actions you can take.
Press `?` at any time to see the help screen, which describes all of the built-in actions you can take.
To get a copy of the source code for this deck, use the `spiel demo copy` command.
"""
@ -338,7 +337,7 @@ def image() -> RenderableType:
Layout(
Panel.fit(
Image.from_file(image_path),
subtitle=str(image_path),
subtitle=image_path.name,
box=HEAVY,
padding=0,
)

@ -27,10 +27,11 @@ class Footer(SpielWidget):
def on_mount(self) -> None:
super().on_mount()
self.update_now()
self.set_interval(1 / 60, self.update_now)
def update_now(self) -> None:
self.now = datetime.now()
self.now = self.app.fixed_time or datetime.now()
@property
def longest_slide_number_length(self) -> int:

Loading…
Cancel
Save