prezo 0.3.1__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 ADDED
@@ -0,0 +1,216 @@
1
+ """Prezo - TUI-based presentation tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ # ANSI color codes for error messages
10
+ RED = "\033[91m"
11
+ YELLOW = "\033[93m"
12
+ BOLD = "\033[1m"
13
+ RESET = "\033[0m"
14
+
15
+
16
+ def _error(message: str) -> None:
17
+ """Print an error message and exit."""
18
+ sys.stderr.write(f"{RED}{BOLD}error:{RESET} {message}\n")
19
+ sys.exit(1)
20
+
21
+
22
+ def _warn(message: str) -> None:
23
+ """Print a warning message."""
24
+ sys.stderr.write(f"{YELLOW}{BOLD}warning:{RESET} {message}\n")
25
+
26
+
27
+ def _validate_file(path: Path, must_exist: bool = True) -> Path:
28
+ """Validate a file path and return the resolved path.
29
+
30
+ Args:
31
+ path: The path to validate.
32
+ must_exist: Whether the file must exist.
33
+
34
+ Returns:
35
+ The resolved absolute path.
36
+
37
+ """
38
+ resolved = path.resolve()
39
+
40
+ if must_exist and not resolved.exists():
41
+ _error(
42
+ f"file not found: {path}\n\n"
43
+ "Make sure the file exists and the path is correct."
44
+ )
45
+
46
+ if must_exist and resolved.is_dir():
47
+ _error(
48
+ f"expected a file, got a directory: {path}\n\nProvide a path to a .md file."
49
+ )
50
+
51
+ if must_exist and resolved.suffix.lower() not in (".md", ".markdown"):
52
+ _warn(
53
+ f"file '{path.name}' does not have a .md extension "
54
+ "- treating as markdown anyway"
55
+ )
56
+
57
+ return resolved
58
+
59
+
60
+ def main() -> None:
61
+ """Entry point for Prezo."""
62
+ parser = argparse.ArgumentParser(
63
+ prog="prezo",
64
+ description="TUI-based presentation tool for Markdown slides",
65
+ epilog="For more information, visit: https://github.com/abilian/prezo",
66
+ )
67
+ parser.add_argument(
68
+ "file",
69
+ nargs="?",
70
+ help="Path to the presentation file (.md)",
71
+ )
72
+ parser.add_argument(
73
+ "--export",
74
+ "-e",
75
+ metavar="FORMAT",
76
+ choices=["pdf", "html", "png", "svg"],
77
+ help="Export presentation to format (pdf, html, png, svg)",
78
+ )
79
+ parser.add_argument(
80
+ "--slide",
81
+ metavar="NUM",
82
+ type=int,
83
+ help="Export only slide number NUM (1-indexed, for png/svg export)",
84
+ )
85
+ parser.add_argument(
86
+ "--output",
87
+ "-o",
88
+ metavar="PATH",
89
+ help="Output path for export (default: same name with new extension)",
90
+ )
91
+ parser.add_argument(
92
+ "--theme",
93
+ "-t",
94
+ metavar="NAME",
95
+ default="dark",
96
+ help="Theme for export (dark, light, dracula, solarized-dark, nord, gruvbox)",
97
+ )
98
+ parser.add_argument(
99
+ "--size",
100
+ "-s",
101
+ metavar="WxH",
102
+ default="80x24",
103
+ help="Screen size for export as WIDTHxHEIGHT (default: 80x24)",
104
+ )
105
+ parser.add_argument(
106
+ "--no-chrome",
107
+ action="store_true",
108
+ help="Export without window decorations (for printing)",
109
+ )
110
+ parser.add_argument(
111
+ "--scale",
112
+ metavar="FACTOR",
113
+ type=float,
114
+ default=2.0,
115
+ help="Scale factor for PNG export (default: 2.0 for higher resolution)",
116
+ )
117
+ parser.add_argument(
118
+ "--no-watch",
119
+ action="store_true",
120
+ help="Disable file watching for auto-reload",
121
+ )
122
+ parser.add_argument(
123
+ "--config",
124
+ "-c",
125
+ metavar="PATH",
126
+ help="Path to custom config file (default: ~/.config/prezo/config.toml)",
127
+ )
128
+ parser.add_argument(
129
+ "--image-mode",
130
+ metavar="MODE",
131
+ choices=["auto", "kitty", "sixel", "iterm", "ascii", "none"],
132
+ help="Image rendering mode (auto, kitty, sixel, iterm, ascii, none)",
133
+ )
134
+
135
+ args = parser.parse_args()
136
+
137
+ # Load config from custom path or default
138
+ from .config import load_config # noqa: PLC0415
139
+
140
+ config_path = Path(args.config) if args.config else None
141
+ if config_path and not config_path.exists():
142
+ _error(f"config file not found: {config_path}")
143
+
144
+ config = load_config(config_path)
145
+
146
+ # Override image mode if specified
147
+ if args.image_mode:
148
+ config.images.mode = args.image_mode
149
+
150
+ if args.export:
151
+ if not args.file:
152
+ _error(
153
+ "--export requires a presentation file\n\n"
154
+ "Usage: prezo -e pdf presentation.md"
155
+ )
156
+
157
+ # Validate source file
158
+ source_path = _validate_file(Path(args.file))
159
+
160
+ # Parse size
161
+ try:
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
+ )
168
+
169
+ if args.export == "html":
170
+ from .export import run_html_export # noqa: PLC0415
171
+
172
+ sys.exit(
173
+ run_html_export(
174
+ str(source_path),
175
+ args.output,
176
+ theme=args.theme,
177
+ ),
178
+ )
179
+ elif args.export in ("png", "svg"):
180
+ from .export import run_image_export # noqa: PLC0415
181
+
182
+ sys.exit(
183
+ run_image_export(
184
+ str(source_path),
185
+ args.output,
186
+ output_format=args.export,
187
+ theme=args.theme,
188
+ width=width,
189
+ height=height,
190
+ chrome=not args.no_chrome,
191
+ slide_num=args.slide,
192
+ scale=args.scale,
193
+ ),
194
+ )
195
+ else:
196
+ from .export import run_export # noqa: PLC0415
197
+
198
+ sys.exit(
199
+ run_export(
200
+ str(source_path),
201
+ args.output,
202
+ theme=args.theme,
203
+ width=width,
204
+ height=height,
205
+ chrome=not args.no_chrome,
206
+ ),
207
+ )
208
+ else:
209
+ from .app import run_app # noqa: PLC0415
210
+
211
+ # Validate file if provided
212
+ file_path = None
213
+ if args.file:
214
+ file_path = _validate_file(Path(args.file))
215
+
216
+ run_app(file_path, watch=not args.no_watch, config=config)