prezo 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
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.
- prezo/__init__.py +23 -8
- prezo/app.py +7 -4
- prezo/config.py +1 -1
- prezo/export.py +4 -2
- prezo/images/ascii.py +3 -3
- prezo/images/kitty.py +2 -1
- prezo/images/overlay.py +2 -1
- prezo/images/sixel.py +3 -3
- prezo/terminal.py +1 -1
- prezo/widgets/image_display.py +2 -2
- prezo/widgets/status_bar.py +6 -2
- {prezo-0.3.1.dist-info → prezo-0.3.2.dist-info}/METADATA +3 -3
- {prezo-0.3.1.dist-info → prezo-0.3.2.dist-info}/RECORD +15 -15
- {prezo-0.3.1.dist-info → prezo-0.3.2.dist-info}/WHEEL +0 -0
- {prezo-0.3.1.dist-info → prezo-0.3.2.dist-info}/entry_points.txt +0 -0
prezo/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import NoReturn
|
|
8
9
|
|
|
9
10
|
# ANSI color codes for error messages
|
|
10
11
|
RED = "\033[91m"
|
|
@@ -13,7 +14,7 @@ BOLD = "\033[1m"
|
|
|
13
14
|
RESET = "\033[0m"
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def _error(message: str) ->
|
|
17
|
+
def _error(message: str) -> NoReturn:
|
|
17
18
|
"""Print an error message and exit."""
|
|
18
19
|
sys.stderr.write(f"{RED}{BOLD}error:{RESET} {message}\n")
|
|
19
20
|
sys.exit(1)
|
|
@@ -24,6 +25,26 @@ def _warn(message: str) -> None:
|
|
|
24
25
|
sys.stderr.write(f"{YELLOW}{BOLD}warning:{RESET} {message}\n")
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def _parse_size(size_str: str) -> tuple[int, int]:
|
|
29
|
+
"""Parse a size string like '80x24' into width and height.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
size_str: Size string in WIDTHxHEIGHT format.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Tuple of (width, height).
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
width, height = map(int, size_str.lower().split("x"))
|
|
40
|
+
return width, height
|
|
41
|
+
except ValueError:
|
|
42
|
+
_error(
|
|
43
|
+
f"invalid size format: {size_str}\n\n"
|
|
44
|
+
"Use WIDTHxHEIGHT format (e.g., 80x24, 100x30)"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
27
48
|
def _validate_file(path: Path, must_exist: bool = True) -> Path:
|
|
28
49
|
"""Validate a file path and return the resolved path.
|
|
29
50
|
|
|
@@ -158,13 +179,7 @@ def main() -> None:
|
|
|
158
179
|
source_path = _validate_file(Path(args.file))
|
|
159
180
|
|
|
160
181
|
# Parse size
|
|
161
|
-
|
|
162
|
-
width, height = map(int, args.size.lower().split("x"))
|
|
163
|
-
except ValueError:
|
|
164
|
-
_error(
|
|
165
|
-
f"invalid size format: {args.size}\n\n"
|
|
166
|
-
"Use WIDTHxHEIGHT format (e.g., 80x24, 100x30)"
|
|
167
|
-
)
|
|
182
|
+
width, height = _parse_size(args.size)
|
|
168
183
|
|
|
169
184
|
if args.export == "html":
|
|
170
185
|
from .export import run_html_export # noqa: PLC0415
|
prezo/app.py
CHANGED
|
@@ -12,9 +12,12 @@ import termios
|
|
|
12
12
|
import tty
|
|
13
13
|
from functools import partial
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import ClassVar
|
|
15
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
16
16
|
|
|
17
17
|
from textual.app import App, ComposeResult
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from textual.timer import Timer
|
|
18
21
|
from textual.binding import Binding, BindingType
|
|
19
22
|
from textual.command import Hit, Hits, Provider
|
|
20
23
|
from textual.containers import Horizontal, Vertical, VerticalScroll
|
|
@@ -196,7 +199,7 @@ class PrezoApp(App):
|
|
|
196
199
|
|
|
197
200
|
ENABLE_COMMAND_PALETTE = True
|
|
198
201
|
COMMAND_PALETTE_BINDING = "ctrl+p"
|
|
199
|
-
COMMANDS: ClassVar[set[type[Provider]]] = {PrezoCommands}
|
|
202
|
+
COMMANDS: ClassVar[set[type[Provider]]] = {PrezoCommands} # type: ignore[assignment]
|
|
200
203
|
|
|
201
204
|
CSS = """
|
|
202
205
|
Screen {
|
|
@@ -371,7 +374,7 @@ class PrezoApp(App):
|
|
|
371
374
|
self.watch_enabled = watch
|
|
372
375
|
|
|
373
376
|
self._file_mtime: float | None = None
|
|
374
|
-
self._watch_timer = None
|
|
377
|
+
self._watch_timer: Timer | None = None
|
|
375
378
|
self._reload_interval = self.config.behavior.reload_interval
|
|
376
379
|
|
|
377
380
|
def compose(self) -> ComposeResult:
|
|
@@ -735,7 +738,7 @@ class PrezoApp(App):
|
|
|
735
738
|
def watch_app_theme(self, theme_name: str) -> None:
|
|
736
739
|
"""Apply theme when it changes."""
|
|
737
740
|
# Only apply to widgets after mount (watcher fires during init)
|
|
738
|
-
if not self.is_mounted:
|
|
741
|
+
if not self.is_mounted: # type: ignore[truthy-function]
|
|
739
742
|
return
|
|
740
743
|
self._apply_theme(theme_name)
|
|
741
744
|
self.notify(f"Theme: {theme_name}", timeout=1)
|
prezo/config.py
CHANGED
prezo/export.py
CHANGED
|
@@ -240,8 +240,8 @@ def export_to_pdf(
|
|
|
240
240
|
return EXPORT_FAILED, "Presentation has no slides"
|
|
241
241
|
|
|
242
242
|
# Create temporary directory for SVG files
|
|
243
|
-
with tempfile.TemporaryDirectory() as
|
|
244
|
-
tmpdir = Path(
|
|
243
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
244
|
+
tmpdir = Path(tmpdir_str)
|
|
245
245
|
svg_files = []
|
|
246
246
|
|
|
247
247
|
# Render each slide to SVG
|
|
@@ -674,6 +674,8 @@ def export_slide_to_image(
|
|
|
674
674
|
bytestring=svg_content.encode("utf-8"),
|
|
675
675
|
scale=scale,
|
|
676
676
|
)
|
|
677
|
+
if png_data is None:
|
|
678
|
+
return EXPORT_FAILED, "PNG conversion returned no data"
|
|
677
679
|
output_path.write_bytes(png_data)
|
|
678
680
|
return EXPORT_SUCCESS, f"Exported slide {slide_num + 1} to {output_path}"
|
|
679
681
|
except Exception as e:
|
prezo/images/ascii.py
CHANGED
|
@@ -54,7 +54,7 @@ class AsciiRenderer:
|
|
|
54
54
|
"""Render image using PIL."""
|
|
55
55
|
from PIL import Image
|
|
56
56
|
|
|
57
|
-
img = Image.open(path)
|
|
57
|
+
img: Image.Image = Image.open(path)
|
|
58
58
|
|
|
59
59
|
# Convert to grayscale
|
|
60
60
|
img = img.convert("L")
|
|
@@ -114,7 +114,7 @@ class ColorAsciiRenderer(AsciiRenderer):
|
|
|
114
114
|
"""Render image as colored ASCII."""
|
|
115
115
|
from PIL import Image
|
|
116
116
|
|
|
117
|
-
img = Image.open(path)
|
|
117
|
+
img: Image.Image = Image.open(path)
|
|
118
118
|
|
|
119
119
|
# Keep color, convert to RGB
|
|
120
120
|
img = img.convert("RGB")
|
|
@@ -178,7 +178,7 @@ class HalfBlockRenderer:
|
|
|
178
178
|
"""Render image using half-blocks."""
|
|
179
179
|
from PIL import Image
|
|
180
180
|
|
|
181
|
-
img = Image.open(path)
|
|
181
|
+
img: Image.Image = Image.open(path)
|
|
182
182
|
img = img.convert("RGB")
|
|
183
183
|
|
|
184
184
|
# Calculate dimensions (height is doubled because of half-blocks)
|
prezo/images/kitty.py
CHANGED
|
@@ -47,13 +47,14 @@ class KittyImageManager:
|
|
|
47
47
|
|
|
48
48
|
_instance: KittyImageManager | None = None
|
|
49
49
|
_next_id: int = 1
|
|
50
|
+
_initialized: bool = False
|
|
50
51
|
|
|
51
52
|
def __new__(cls) -> Self:
|
|
52
53
|
"""Create or return singleton instance."""
|
|
53
54
|
if cls._instance is None:
|
|
54
55
|
cls._instance = super().__new__(cls)
|
|
55
56
|
cls._instance._initialized = False
|
|
56
|
-
return cls._instance
|
|
57
|
+
return cls._instance # type: ignore[return-value]
|
|
57
58
|
|
|
58
59
|
def __init__(self) -> None:
|
|
59
60
|
"""Initialize the Kitty image manager."""
|
prezo/images/overlay.py
CHANGED
|
@@ -45,13 +45,14 @@ class ImageOverlayRenderer:
|
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
47
|
_instance: ImageOverlayRenderer | None = None
|
|
48
|
+
_initialized: bool = False
|
|
48
49
|
|
|
49
50
|
def __new__(cls) -> Self:
|
|
50
51
|
"""Create or return singleton instance."""
|
|
51
52
|
if cls._instance is None:
|
|
52
53
|
cls._instance = super().__new__(cls)
|
|
53
54
|
cls._instance._initialized = False
|
|
54
|
-
return cls._instance
|
|
55
|
+
return cls._instance # type: ignore[return-value]
|
|
55
56
|
|
|
56
57
|
def __init__(self) -> None:
|
|
57
58
|
"""Initialize the image overlay renderer."""
|
prezo/images/sixel.py
CHANGED
|
@@ -53,7 +53,7 @@ class SixelRenderer:
|
|
|
53
53
|
from PIL import Image
|
|
54
54
|
|
|
55
55
|
# Load and resize image
|
|
56
|
-
img = Image.open(path)
|
|
56
|
+
img: Image.Image = Image.open(path)
|
|
57
57
|
img = img.convert("RGB")
|
|
58
58
|
|
|
59
59
|
# Calculate pixel dimensions
|
|
@@ -89,8 +89,8 @@ class SixelRenderer:
|
|
|
89
89
|
"""
|
|
90
90
|
from PIL import Image
|
|
91
91
|
|
|
92
|
-
img = Image.open(path)
|
|
93
|
-
img = img.convert("P", palette=Image.ADAPTIVE, colors=256)
|
|
92
|
+
img: Image.Image = Image.open(path)
|
|
93
|
+
img = img.convert("P", palette=Image.Palette.ADAPTIVE, colors=256)
|
|
94
94
|
|
|
95
95
|
# Calculate pixel dimensions
|
|
96
96
|
pixel_width = width * 8
|
prezo/terminal.py
CHANGED
|
@@ -128,7 +128,7 @@ def supports_true_color() -> bool:
|
|
|
128
128
|
return _is_iterm() or _is_kitty()
|
|
129
129
|
|
|
130
130
|
|
|
131
|
-
def get_capability_summary() -> dict[str, bool | str]:
|
|
131
|
+
def get_capability_summary() -> dict[str, bool | int | str]:
|
|
132
132
|
"""Get a summary of terminal capabilities.
|
|
133
133
|
|
|
134
134
|
Returns:
|
prezo/widgets/image_display.py
CHANGED
|
@@ -6,7 +6,7 @@ Falls back to Unicode halfcell rendering for unsupported terminals.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
10
|
|
|
11
11
|
from textual.widgets import Static
|
|
12
12
|
from textual_image.widget import Image as TextualImage
|
|
@@ -68,7 +68,7 @@ class ImageDisplay(Static):
|
|
|
68
68
|
self._image_path: Path | str | None = image_path
|
|
69
69
|
self._width: int | None = width
|
|
70
70
|
self._height: int | None = height
|
|
71
|
-
self._image_widget:
|
|
71
|
+
self._image_widget: Any = None # TextualImage | None
|
|
72
72
|
|
|
73
73
|
def compose(self) -> ComposeResult:
|
|
74
74
|
"""Compose the image widget."""
|
prezo/widgets/status_bar.py
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
6
7
|
|
|
7
8
|
from textual.reactive import reactive
|
|
8
9
|
from textual.widgets import Static
|
|
9
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from textual.timer import Timer
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
def format_progress_bar(current: int, total: int, width: int = 20) -> str:
|
|
12
16
|
"""Generate a progress bar string.
|
|
@@ -57,7 +61,7 @@ class StatusBar(Static):
|
|
|
57
61
|
"""Initialize the status bar."""
|
|
58
62
|
super().__init__(**kwargs)
|
|
59
63
|
self._start_time: datetime | None = None
|
|
60
|
-
self._timer = None
|
|
64
|
+
self._timer: Timer | None = None
|
|
61
65
|
|
|
62
66
|
def on_mount(self) -> None:
|
|
63
67
|
"""Start the timer on mount."""
|
|
@@ -185,7 +189,7 @@ class ClockDisplay(Static):
|
|
|
185
189
|
"""Initialize the clock display."""
|
|
186
190
|
super().__init__(**kwargs)
|
|
187
191
|
self._start_time: datetime | None = None
|
|
188
|
-
self._timer = None
|
|
192
|
+
self._timer: Timer | None = None
|
|
189
193
|
|
|
190
194
|
def on_mount(self) -> None:
|
|
191
195
|
"""Start the clock timer on mount."""
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: prezo
|
|
3
|
-
Version: 0.3.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: A TUI-based presentation tool for the terminal, built with Textual.
|
|
5
5
|
Author: Stefane Fermigier
|
|
6
6
|
Author-email: Stefane Fermigier <sf@fermigier.com>
|
|
7
7
|
Requires-Dist: textual>=0.89.1
|
|
8
|
-
Requires-Dist: python-frontmatter>=1.1.0
|
|
9
8
|
Requires-Dist: textual-image>=0.8.0
|
|
9
|
+
Requires-Dist: python-frontmatter>=1.1.0
|
|
10
10
|
Requires-Python: >=3.12
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
prezo/__init__.py,sha256=
|
|
2
|
-
prezo/app.py,sha256=
|
|
3
|
-
prezo/config.py,sha256=
|
|
4
|
-
prezo/export.py,sha256=
|
|
1
|
+
prezo/__init__.py,sha256=blmZoW_bPndFLVT4UZzrfAp8IaGuvmFhtNGUs0nHpFQ,6434
|
|
2
|
+
prezo/app.py,sha256=5D2o3VnrIODp25HzQcxTrIAkkSD5zPtpqb9t7sReT4o,32082
|
|
3
|
+
prezo/config.py,sha256=byBBFHZU3fkq3dRfg5h4zG_eihbi7lHZkriSv-g-ogY,6672
|
|
4
|
+
prezo/export.py,sha256=1npeBV3m9_eTGhYHG7G-ekDGIRX4vNBgtt2RwpjLydc,23324
|
|
5
5
|
prezo/images/__init__.py,sha256=xrWSR3z0SXYpLtjIvR2VOMxiJGkxEsls5-EOs9GecFA,324
|
|
6
|
-
prezo/images/ascii.py,sha256=
|
|
6
|
+
prezo/images/ascii.py,sha256=wA20WKpjdTzbdYZMEX-p-jH0LmCJYAA8vx1pBojPhXI,7381
|
|
7
7
|
prezo/images/base.py,sha256=STuS57AVSJ2lzwyn0QrIceGaSd2IWEiLGN-elT3u3AM,2749
|
|
8
8
|
prezo/images/chafa.py,sha256=rqqctIw5xQarEYz5SR-2a5ePJ3xbm0a3NWiLwxNBEUE,3726
|
|
9
9
|
prezo/images/iterm.py,sha256=bSIN6qfOt3URTjbV-d963K2bX9KdfT5cBQQOrIivZPs,3742
|
|
10
|
-
prezo/images/kitty.py,sha256=
|
|
11
|
-
prezo/images/overlay.py,sha256=
|
|
10
|
+
prezo/images/kitty.py,sha256=mWR-tIE_WDP5BjOkQydPpxWBBGNaZL8PkMICesWQid8,10883
|
|
11
|
+
prezo/images/overlay.py,sha256=lWIAvINxZrKembtB0gzWWaUoedNt7beFU4OhErfwWaw,9600
|
|
12
12
|
prezo/images/processor.py,sha256=zMcfwltecup_AX2FhUIlPdO7c87a9jw7P9tLTIkr54U,4069
|
|
13
|
-
prezo/images/sixel.py,sha256=
|
|
13
|
+
prezo/images/sixel.py,sha256=cV5X7wPiAt2PmRvDEha4GU_xKH6oq0OukY_suLsLsvU,5518
|
|
14
14
|
prezo/parser.py,sha256=bD2MecHm7EssHd5LB2Bw6JuUqbjWPztWUu2meYwsyIQ,14793
|
|
15
15
|
prezo/screens/__init__.py,sha256=xHG9jNJz4vi1tpneSEVlD0D9I0M2U4gAGk6-R9xbUf4,508
|
|
16
16
|
prezo/screens/base.py,sha256=2n6Uj8evfIbcpn4AVYNG5iM_k7rIJ3Vwmor_xrQPU9E,2057
|
|
@@ -20,13 +20,13 @@ prezo/screens/help.py,sha256=fjwHp9qPMmyRIaME-Bcz-g6bn8UrtbL_Dk269QSU-zs,2987
|
|
|
20
20
|
prezo/screens/overview.py,sha256=s9-ifbcnXYhbxb_Kl2UhpB3IE7msInX6LWB-J1dazLo,5382
|
|
21
21
|
prezo/screens/search.py,sha256=3YG9WLGEIKW3YHpM0K1lgwhuqBveXd8ZoQZ178_zGd4,7809
|
|
22
22
|
prezo/screens/toc.py,sha256=8WYb5nbgP9agY-hUTATxLU4X1uka_bc2MN86hFW4aRg,8241
|
|
23
|
-
prezo/terminal.py,sha256=
|
|
23
|
+
prezo/terminal.py,sha256=Z3DuuighY-qfF6GWH_AkR5RnAc5Gj3LsPS266VNj7Pk,3638
|
|
24
24
|
prezo/themes.py,sha256=3keUgheOsNGjS0uCjRv7az9sVSnrz5tc-jZ58YNB7tg,3070
|
|
25
25
|
prezo/widgets/__init__.py,sha256=UeTHBgPDvqTkK5tTsPXhdJXP3qZefnltKtUtvJBx9m0,295
|
|
26
|
-
prezo/widgets/image_display.py,sha256=
|
|
26
|
+
prezo/widgets/image_display.py,sha256=8IKncaoC2iWebmJQp_QomF7UVgRxD4WThOshN1Nht2M,3361
|
|
27
27
|
prezo/widgets/slide_button.py,sha256=g5mvtCZSorTIZp_PXgHYeYeeCSNFy0pW3K7iDlZu7yA,2012
|
|
28
|
-
prezo/widgets/status_bar.py,sha256=
|
|
29
|
-
prezo-0.3.
|
|
30
|
-
prezo-0.3.
|
|
31
|
-
prezo-0.3.
|
|
32
|
-
prezo-0.3.
|
|
28
|
+
prezo/widgets/status_bar.py,sha256=Wcun71kg2Q4s5aduPwTvS4kDHZj5p-zDmD7Cx3_ZFP4,8136
|
|
29
|
+
prezo-0.3.2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
30
|
+
prezo-0.3.2.dist-info/entry_points.txt,sha256=74ShZJ_EKjzi63JyPynVnc0uCHGNjIWjAVs8vU_qTyA,38
|
|
31
|
+
prezo-0.3.2.dist-info/METADATA,sha256=f2T5YoA19gKl2ieEHDaVPIegl5Ky8NWTWarr8GDkhEE,4873
|
|
32
|
+
prezo-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|