prezento 1.0__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.
- prezento/__init__.py +1 -0
- prezento/main.py +479 -0
- prezento-1.0.dist-info/METADATA +169 -0
- prezento-1.0.dist-info/RECORD +7 -0
- prezento-1.0.dist-info/WHEEL +5 -0
- prezento-1.0.dist-info/entry_points.txt +2 -0
- prezento-1.0.dist-info/top_level.txt +1 -0
prezento/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
entry_points = {'console_scripts': ['prezento=prezento:main', ]}
|
prezento/main.py
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
# prezento – Modern RST → HTML slide generator
|
|
2
|
+
# Uses b6plus instead of impress.js
|
|
3
|
+
# Outputs: .html, .substep.pdf.html, .presentation.html
|
|
4
|
+
|
|
5
|
+
# [ADDED] Imported 're' (regular expressions) to help manipulate the SVG strings natively.
|
|
6
|
+
import os
|
|
7
|
+
import argparse
|
|
8
|
+
import textwrap
|
|
9
|
+
import copy
|
|
10
|
+
import re
|
|
11
|
+
import graphviz
|
|
12
|
+
from docutils import nodes
|
|
13
|
+
from docutils.parsers.rst import Directive, directives
|
|
14
|
+
from docutils.core import publish_doctree, publish_from_doctree
|
|
15
|
+
from docutils.writers.html5_polyglot import Writer as HTML5WriterBase, HTMLTranslator
|
|
16
|
+
|
|
17
|
+
# ── Custom nodes ─────────────────────────────────────────────────────────────
|
|
18
|
+
class slido_block(nodes.container):
|
|
19
|
+
pass
|
|
20
|
+
class graphviz_block(nodes.General, nodes.Element):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
# ── Directives ───────────────────────────────────────────────────────────────
|
|
24
|
+
class PrezentoDirective(Directive):
|
|
25
|
+
has_content = True
|
|
26
|
+
optional_arguments = 10
|
|
27
|
+
final_argument_whitespace = True
|
|
28
|
+
option_spec = {
|
|
29
|
+
'css': directives.unchanged,
|
|
30
|
+
'js': directives.unchanged,
|
|
31
|
+
'width': directives.unchanged,
|
|
32
|
+
'height': directives.unchanged,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
def run(self):
|
|
36
|
+
config = self.options.copy()
|
|
37
|
+
if self.arguments:
|
|
38
|
+
config['title'] = ' '.join(self.arguments)
|
|
39
|
+
self.state.document.presentation_config = config
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SlidoDirective(Directive):
|
|
44
|
+
optional_arguments = 10
|
|
45
|
+
final_argument_whitespace = True
|
|
46
|
+
has_content = True
|
|
47
|
+
option_spec = {
|
|
48
|
+
'class': directives.class_option,
|
|
49
|
+
'id': directives.unchanged,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def run(self):
|
|
53
|
+
node = slido_block()
|
|
54
|
+
if self.arguments:
|
|
55
|
+
node['title'] = ' '.join(self.arguments)
|
|
56
|
+
if 'class' in self.options:
|
|
57
|
+
node['classes'] = self.options['class']
|
|
58
|
+
if 'id' in self.options:
|
|
59
|
+
node['ids'] = [self.options['id']]
|
|
60
|
+
|
|
61
|
+
# Dedent content
|
|
62
|
+
text = '\n'.join(self.content)
|
|
63
|
+
dedented = textwrap.dedent(text)
|
|
64
|
+
content = self.content.__class__(
|
|
65
|
+
dedented.splitlines(), source=self.state.document['source']
|
|
66
|
+
)
|
|
67
|
+
self.state.nested_parse(content, self.content_offset, node)
|
|
68
|
+
return [node]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GraphvizDirective(Directive):
|
|
72
|
+
has_content = True
|
|
73
|
+
# [CHANGED] Expanded option_spec to include class, width, and height.
|
|
74
|
+
# This mirrors the built-in 'image' directive capabilities.
|
|
75
|
+
option_spec = {
|
|
76
|
+
'align': directives.unchanged,
|
|
77
|
+
'class': directives.class_option,
|
|
78
|
+
'width': directives.unchanged,
|
|
79
|
+
'height': directives.unchanged
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def run(self):
|
|
83
|
+
node = graphviz_block()
|
|
84
|
+
|
|
85
|
+
# [ADDED] Store the parsed options into the node so the HTML translator can access them.
|
|
86
|
+
if 'class' in self.options:
|
|
87
|
+
node['classes'] = self.options['class']
|
|
88
|
+
if 'align' in self.options:
|
|
89
|
+
node['align'] = self.options['align']
|
|
90
|
+
if 'width' in self.options:
|
|
91
|
+
node['width'] = self.options['width']
|
|
92
|
+
if 'height' in self.options:
|
|
93
|
+
node['height'] = self.options['height']
|
|
94
|
+
|
|
95
|
+
dot_code = '\n'.join(self.content)
|
|
96
|
+
try:
|
|
97
|
+
svg = graphviz.Source(dot_code).pipe(format='svg').decode('utf-8')
|
|
98
|
+
if '<svg' in svg:
|
|
99
|
+
svg = svg[svg.find('<svg'):]
|
|
100
|
+
|
|
101
|
+
# [ADDED] Graphviz hardcodes width and height into the <svg> tag in points (pt),
|
|
102
|
+
# which overrides external CSS. If the user specifies a width or height in the
|
|
103
|
+
# RST directive, we strip the Graphviz defaults and inject the user's values directly.
|
|
104
|
+
if 'width' in self.options or 'height' in self.options:
|
|
105
|
+
# Remove the original width and height attributes using regex
|
|
106
|
+
svg = re.sub(r'(<svg[^>]*?)\s+width="[^"]+"', r'\1', svg, count=1)
|
|
107
|
+
svg = re.sub(r'(<svg[^>]*?)\s+height="[^"]+"', r'\1', svg, count=1)
|
|
108
|
+
|
|
109
|
+
# Build the new attribute string
|
|
110
|
+
new_attrs = ""
|
|
111
|
+
if 'width' in self.options:
|
|
112
|
+
new_attrs += f' width="{self.options["width"]}"'
|
|
113
|
+
if 'height' in self.options:
|
|
114
|
+
new_attrs += f' height="{self.options["height"]}"'
|
|
115
|
+
|
|
116
|
+
# Inject the new dimensions right after the opening '<svg'
|
|
117
|
+
svg = svg.replace('<svg', f'<svg{new_attrs}', 1)
|
|
118
|
+
|
|
119
|
+
node['svg'] = svg
|
|
120
|
+
except Exception:
|
|
121
|
+
node['svg'] = ''
|
|
122
|
+
return [node]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
directives.register_directive('prezento', PrezentoDirective)
|
|
126
|
+
directives.register_directive('slido', SlidoDirective)
|
|
127
|
+
directives.register_directive('yographviz', GraphvizDirective)
|
|
128
|
+
|
|
129
|
+
# ── Substep Helpers ──────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
_SUBSTEP_CONTAINER_TYPES = (
|
|
132
|
+
slido_block,
|
|
133
|
+
nodes.container,
|
|
134
|
+
nodes.block_quote,
|
|
135
|
+
nodes.bullet_list,
|
|
136
|
+
nodes.enumerated_list,
|
|
137
|
+
nodes.definition_list,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _is_substep_container(node):
|
|
142
|
+
return (
|
|
143
|
+
isinstance(node, _SUBSTEP_CONTAINER_TYPES)
|
|
144
|
+
and isinstance(node, nodes.Element)
|
|
145
|
+
and 'substep' in node.get('classes', [])
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _is_atomic_substep(node):
|
|
150
|
+
return (
|
|
151
|
+
isinstance(node, nodes.Element)
|
|
152
|
+
and 'substep' in node.get('classes', [])
|
|
153
|
+
and not _is_substep_container(node)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# ── Substep PDF Expansion ───────────────────────────────────────────────────
|
|
157
|
+
def _assign_reveal_indices(root):
|
|
158
|
+
counter = [0]
|
|
159
|
+
|
|
160
|
+
def walk(node):
|
|
161
|
+
if _is_substep_container(node):
|
|
162
|
+
for child in node.children:
|
|
163
|
+
if isinstance(child, nodes.Text):
|
|
164
|
+
continue
|
|
165
|
+
counter[0] += 1
|
|
166
|
+
child['_reveal_index'] = counter[0]
|
|
167
|
+
walk(child)
|
|
168
|
+
elif _is_atomic_substep(node):
|
|
169
|
+
counter[0] += 1
|
|
170
|
+
node['_reveal_index'] = counter[0]
|
|
171
|
+
else:
|
|
172
|
+
for child in node.children:
|
|
173
|
+
if not isinstance(child, nodes.Text):
|
|
174
|
+
walk(child)
|
|
175
|
+
|
|
176
|
+
walk(root)
|
|
177
|
+
return counter[0]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _apply_step_visibility(root, step):
|
|
181
|
+
for node in root.findall(nodes.Element):
|
|
182
|
+
ri = node.get('_reveal_index', 0)
|
|
183
|
+
if ri == 0:
|
|
184
|
+
continue
|
|
185
|
+
classes = [c for c in node.get('classes', []) if c not in ('substep', 'substep-hidden')]
|
|
186
|
+
if ri > step:
|
|
187
|
+
classes.append('substep-hidden')
|
|
188
|
+
node['classes'] = classes
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _deep_clone(node):
|
|
192
|
+
orig_parent = node.parent
|
|
193
|
+
node.parent = None
|
|
194
|
+
try:
|
|
195
|
+
return copy.deepcopy(node)
|
|
196
|
+
finally:
|
|
197
|
+
node.parent = orig_parent
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _expand_slide(slide, slide_number):
|
|
201
|
+
template = _deep_clone(slide)
|
|
202
|
+
total = _assign_reveal_indices(template)
|
|
203
|
+
if total == 0:
|
|
204
|
+
slide['_slide_number'] = slide_number
|
|
205
|
+
return [slide]
|
|
206
|
+
|
|
207
|
+
sections = []
|
|
208
|
+
for step in range(1, total + 1):
|
|
209
|
+
sec = copy.deepcopy(template)
|
|
210
|
+
_apply_step_visibility(sec, step)
|
|
211
|
+
sec['_slide_number'] = slide_number
|
|
212
|
+
sections.append(sec)
|
|
213
|
+
return sections
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _expand_document_for_substep_pdf(document):
|
|
217
|
+
new_children = []
|
|
218
|
+
slide_num = 0
|
|
219
|
+
for node in list(document.children):
|
|
220
|
+
if isinstance(node, slido_block):
|
|
221
|
+
slide_num += 1
|
|
222
|
+
new_children.extend(_expand_slide(node, slide_num))
|
|
223
|
+
else:
|
|
224
|
+
new_children.append(node)
|
|
225
|
+
document.children = new_children
|
|
226
|
+
for child in document.children:
|
|
227
|
+
child.parent = document
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ── b6plus Transformation ───────────────────────────────────────────────────
|
|
231
|
+
def _b6_transform(document):
|
|
232
|
+
"""Convert substep semantics to b6plus `incremental` / `next` classes."""
|
|
233
|
+
|
|
234
|
+
# Phase 1: Handle slido blocks with substep
|
|
235
|
+
for slide in document.findall(slido_block):
|
|
236
|
+
classes = slide.get('classes', [])
|
|
237
|
+
if 'substep' not in classes:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
slide['classes'] = [c for c in classes if c != 'substep']
|
|
241
|
+
|
|
242
|
+
for child in slide.children:
|
|
243
|
+
if not isinstance(child, nodes.Element):
|
|
244
|
+
continue
|
|
245
|
+
if isinstance(child, (nodes.title, nodes.colspec, nodes.thead)):
|
|
246
|
+
continue
|
|
247
|
+
child_classes = list(child.get('classes', []))
|
|
248
|
+
if 'next' not in child_classes:
|
|
249
|
+
child['classes'] = child_classes + ['next']
|
|
250
|
+
|
|
251
|
+
# Phase 2: Other substep containers and atomic elements
|
|
252
|
+
for node in document.findall(nodes.Element):
|
|
253
|
+
classes = node.get('classes', [])
|
|
254
|
+
if 'substep' not in classes:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
clean = [c for c in classes if c != 'substep']
|
|
258
|
+
|
|
259
|
+
if _is_substep_container(node):
|
|
260
|
+
if 'incremental' not in clean:
|
|
261
|
+
clean.append('incremental')
|
|
262
|
+
else:
|
|
263
|
+
if 'next' not in clean:
|
|
264
|
+
clean.append('next')
|
|
265
|
+
|
|
266
|
+
node['classes'] = clean
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ── CSS & Assets ─────────────────────────────────────────────────────────────
|
|
270
|
+
_CSS_FULLWIDTH = (
|
|
271
|
+
'<style>body,footer,header{'
|
|
272
|
+
'max-width:none!important;width:100%;padding:1px 2%;margin:0 auto;'
|
|
273
|
+
'}</style>'
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
_CSS_SUBSTEP_HIDDEN = '<style>.substep-hidden{opacity:0;}</style>'
|
|
277
|
+
|
|
278
|
+
_CSS_B6PLUS = (
|
|
279
|
+
'<style>'
|
|
280
|
+
'body.full .next:not(.active):not(.visited),'
|
|
281
|
+
'body.full .incremental>*:not(.active):not(.visited),'
|
|
282
|
+
'body.full .overlay>*:not(.active):not(.visited){visibility:hidden}'
|
|
283
|
+
'body.full .slide-number{display:none}'
|
|
284
|
+
'body.full section.slide{padding-bottom:1rem;break-after:auto;background-color:#ffffff;}'
|
|
285
|
+
'</style>'
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
_B6PLUS_JS_URL = 'assets/b6plus.js'
|
|
289
|
+
_SIMPLE_CSS_URL = 'assets/style.css'
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ── Translators ──────────────────────────────────────────────────────────────
|
|
293
|
+
class SlidoTranslator(HTMLTranslator):
|
|
294
|
+
def __init__(self, document, output_type='standard'):
|
|
295
|
+
super().__init__(document)
|
|
296
|
+
self.slide_count = 0
|
|
297
|
+
self.config = getattr(document, 'presentation_config', {})
|
|
298
|
+
self.output_type = output_type
|
|
299
|
+
|
|
300
|
+
def visit_document(self, node):
|
|
301
|
+
super().visit_document(node)
|
|
302
|
+
self.head.append(_CSS_FULLWIDTH)
|
|
303
|
+
if self.output_type == 'substep':
|
|
304
|
+
self.head.append(_CSS_SUBSTEP_HIDDEN)
|
|
305
|
+
|
|
306
|
+
cfg = self.config
|
|
307
|
+
if 'title' in cfg:
|
|
308
|
+
self.head.append(f'<title>{cfg["title"]}</title>')
|
|
309
|
+
if 'css' in cfg:
|
|
310
|
+
for css in cfg['css'].split(','):
|
|
311
|
+
self.head.append(f'<link rel="stylesheet" href="{css.strip()}" type="text/css" />')
|
|
312
|
+
if 'js' in cfg:
|
|
313
|
+
for js in cfg['js'].split(','):
|
|
314
|
+
self.head.append(f'<script src="{js.strip()}"></script>')
|
|
315
|
+
|
|
316
|
+
def depart_document(self, node):
|
|
317
|
+
super().depart_document(node)
|
|
318
|
+
self.body_prefix = [x.replace('<main>', '') for x in self.body_prefix]
|
|
319
|
+
self.body_suffix = [x.replace('</main>\n', '').replace('</main>', '') for x in self.body_suffix]
|
|
320
|
+
|
|
321
|
+
def visit_slido_block(self, node):
|
|
322
|
+
if '_slide_number' in node:
|
|
323
|
+
self.slide_count = node['_slide_number']
|
|
324
|
+
else:
|
|
325
|
+
self.slide_count += 1
|
|
326
|
+
|
|
327
|
+
extra = [c for c in node.get('classes', []) if c not in ('substep', 'substep-hidden')]
|
|
328
|
+
class_str = ' '.join(['slide'] + extra)
|
|
329
|
+
id_attr = f' id="{node["ids"][0]}"' if node.get('ids') else ''
|
|
330
|
+
self.body.append(f'<section class="{class_str}"{id_attr}>\n')
|
|
331
|
+
if node.get('title'):
|
|
332
|
+
self.body.append(f'<h2>{node["title"]}</h2>\n')
|
|
333
|
+
|
|
334
|
+
def depart_slido_block(self, node):
|
|
335
|
+
self.body.append(f'<div class="slide-number">{self.slide_count}</div></section>\n')
|
|
336
|
+
|
|
337
|
+
def visit_graphviz_block(self, node):
|
|
338
|
+
# [CHANGED] Calculate container styles dynamically based on the options passed.
|
|
339
|
+
align = node.get('align', 'center')
|
|
340
|
+
|
|
341
|
+
# [ADDED] Merge the default container class with any user-supplied classes
|
|
342
|
+
classes = ['graphviz-container'] + node.get('classes', [])
|
|
343
|
+
|
|
344
|
+
# [ADDED] Build inline styling to handle the alignment
|
|
345
|
+
styles = []
|
|
346
|
+
if align == 'center':
|
|
347
|
+
styles.append('margin: 0 auto;')
|
|
348
|
+
styles.append('text-align: center;')
|
|
349
|
+
elif align == 'left':
|
|
350
|
+
styles.append('margin-right: auto;')
|
|
351
|
+
styles.append('text-align: left;')
|
|
352
|
+
elif align == 'right':
|
|
353
|
+
styles.append('margin-left: auto;')
|
|
354
|
+
styles.append('text-align: right;')
|
|
355
|
+
|
|
356
|
+
class_str = ' '.join(classes)
|
|
357
|
+
style_str = ' '.join(styles)
|
|
358
|
+
|
|
359
|
+
# [CHANGED] Output the customized div with the dynamic styles and classes
|
|
360
|
+
self.body.append(f'<div class="{class_str}" style="{style_str}">\n{node.get("svg", "")}\n</div>\n')
|
|
361
|
+
raise nodes.SkipNode
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class PresentationSlidoTranslator(SlidoTranslator):
|
|
365
|
+
def __init__(self, document):
|
|
366
|
+
HTMLTranslator.__init__(self, document)
|
|
367
|
+
self.slide_count = 0
|
|
368
|
+
self.config = getattr(document, 'presentation_config', {})
|
|
369
|
+
self._progress_emitted = False
|
|
370
|
+
|
|
371
|
+
def visit_document(self, node):
|
|
372
|
+
HTMLTranslator.visit_document(self, node)
|
|
373
|
+
cfg = self.config
|
|
374
|
+
|
|
375
|
+
if 'title' in cfg:
|
|
376
|
+
self.head.append(f'<title>{cfg["title"]}</title>')
|
|
377
|
+
|
|
378
|
+
# 1. b6plus framework CSS first
|
|
379
|
+
self.head.append(f'<link rel="stylesheet" href="{_SIMPLE_CSS_URL}" />')
|
|
380
|
+
|
|
381
|
+
# 2. User CSS (can override b6plus)
|
|
382
|
+
if 'css' in cfg:
|
|
383
|
+
for css in cfg['css'].split(','):
|
|
384
|
+
self.head.append(
|
|
385
|
+
f'<link rel="stylesheet" href="{css.strip()}" type="text/css" />'
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
self.head.append(_CSS_FULLWIDTH)
|
|
389
|
+
self.head.append(_CSS_B6PLUS)
|
|
390
|
+
|
|
391
|
+
# 3. b6plus script
|
|
392
|
+
self.head.append(f'<script src="{_B6PLUS_JS_URL}"></script>')
|
|
393
|
+
|
|
394
|
+
# 4. Additional user JS
|
|
395
|
+
if 'js' in cfg:
|
|
396
|
+
for js in cfg['js'].split(','):
|
|
397
|
+
self.head.append(f'<script src="{js.strip()}"></script>')
|
|
398
|
+
|
|
399
|
+
self.body.append(
|
|
400
|
+
'<script>\n'
|
|
401
|
+
'document.addEventListener("DOMContentLoaded", function() {\n'
|
|
402
|
+
' setTimeout(function() {\n'
|
|
403
|
+
' if (typeof b6plus !== "undefined" && typeof b6plus.init === "function") {\n'
|
|
404
|
+
' b6plus.init();\n'
|
|
405
|
+
' } else if (typeof b6plus !== "undefined") {\n'
|
|
406
|
+
' console.log("b6plus loaded - auto mode");\n'
|
|
407
|
+
' }\n'
|
|
408
|
+
' }, 10);\n'
|
|
409
|
+
'});\n'
|
|
410
|
+
'</script>\n'
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# ── Writers ──────────────────────────────────────────────────────────────────
|
|
414
|
+
class SlidoWriter(HTML5WriterBase):
|
|
415
|
+
def __init__(self, output_type='standard'):
|
|
416
|
+
super().__init__()
|
|
417
|
+
self._output_type = output_type
|
|
418
|
+
self.translator_class = lambda doc: SlidoTranslator(doc, output_type=output_type)
|
|
419
|
+
|
|
420
|
+
def translate(self):
|
|
421
|
+
if self._output_type == 'substep':
|
|
422
|
+
_expand_document_for_substep_pdf(self.document)
|
|
423
|
+
super().translate()
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class PresentationSlidoWriter(HTML5WriterBase):
|
|
427
|
+
def __init__(self):
|
|
428
|
+
super().__init__()
|
|
429
|
+
self.translator_class = PresentationSlidoTranslator
|
|
430
|
+
|
|
431
|
+
def translate(self):
|
|
432
|
+
_b6_transform(self.document)
|
|
433
|
+
super().translate()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# ── Public API ───────────────────────────────────────────────────────────────
|
|
437
|
+
def publish_to_html(source_rst: str, output_type: str = 'standard') -> bytes:
|
|
438
|
+
doctree = publish_doctree(source_rst)
|
|
439
|
+
if output_type == 'presentation':
|
|
440
|
+
writer = PresentationSlidoWriter()
|
|
441
|
+
else:
|
|
442
|
+
writer = SlidoWriter(output_type=output_type)
|
|
443
|
+
return publish_from_doctree(doctree, writer=writer)
|
|
444
|
+
|
|
445
|
+
def main():
|
|
446
|
+
parser = argparse.ArgumentParser(description='prezentprogramo v2')
|
|
447
|
+
parser.add_argument('input_file')
|
|
448
|
+
parser.add_argument('-o', '--output')
|
|
449
|
+
parser.add_argument('--no-substep', action='store_true')
|
|
450
|
+
parser.add_argument('--no-presentation', action='store_true')
|
|
451
|
+
args = parser.parse_args()
|
|
452
|
+
|
|
453
|
+
with open(args.input_file, 'r', encoding='utf-8') as f:
|
|
454
|
+
source = f.read()
|
|
455
|
+
|
|
456
|
+
base = os.path.splitext(args.input_file)[0]
|
|
457
|
+
|
|
458
|
+
# Standard
|
|
459
|
+
out = args.output or (base + '.html')
|
|
460
|
+
with open(out, 'w', encoding='utf-8') as f:
|
|
461
|
+
f.write(publish_to_html(source).decode('utf-8'))
|
|
462
|
+
print(f'Written: {out}')
|
|
463
|
+
|
|
464
|
+
if not args.no_substep:
|
|
465
|
+
sub = base + '.substep.pdf.html'
|
|
466
|
+
with open(sub, 'w', encoding='utf-8') as f:
|
|
467
|
+
f.write(publish_to_html(source, 'substep').decode('utf-8'))
|
|
468
|
+
print(f'Written: {sub}')
|
|
469
|
+
|
|
470
|
+
if not args.no_presentation:
|
|
471
|
+
pres = base + '.presentation.html'
|
|
472
|
+
with open(pres, 'w', encoding='utf-8') as f:
|
|
473
|
+
f.write(publish_to_html(source, 'presentation').decode('utf-8'))
|
|
474
|
+
print(f'Written: {pres}')
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# ── CLI ──────────────────────────────────────────────────────────────────────
|
|
478
|
+
if __name__ == '__main__':
|
|
479
|
+
main()
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prezento
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Another text based (rst) presentation tool
|
|
5
|
+
Author-email: Ahmad Yoosofan <yoosofan@gmx.com>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/yoosofan/prezento
|
|
8
|
+
Project-URL: Issues, https://github.com/yoosofan/prezento/issues
|
|
9
|
+
Project-URL: Source, https://github.com/yoosofan/prezento
|
|
10
|
+
Keywords: presentations,restructuredtext,prezento
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Topic :: Multimedia :: Graphics :: Presentation
|
|
15
|
+
Classifier: Topic :: Text Processing
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/x-rst
|
|
24
|
+
Requires-Dist: docutils>=0.21
|
|
25
|
+
Requires-Dist: pygments>=2.0
|
|
26
|
+
Requires-Dist: graphviz>=0.20
|
|
27
|
+
|
|
28
|
+
======================================
|
|
29
|
+
prezento — Modern RST Slide Generator
|
|
30
|
+
======================================
|
|
31
|
+
|
|
32
|
+
**prezento** is a powerful, offline-first slide generator that converts reStructuredText (RST) files into beautiful, interactive HTML presentations.
|
|
33
|
+
|
|
34
|
+
It is a complete rewrite of `prezentprogramo` (a former fork of Hovercraft) with modern architecture, improved substep handling, and a switch from impress.js to **b6plus**.
|
|
35
|
+
|
|
36
|
+
.. image:: https://img.shields.io/badge/License-GPLv3-blue.svg
|
|
37
|
+
:target: LICENSE
|
|
38
|
+
|
|
39
|
+
.. contents:: Table of Contents
|
|
40
|
+
:depth: 2
|
|
41
|
+
|
|
42
|
+
Features
|
|
43
|
+
========
|
|
44
|
+
|
|
45
|
+
* Clean and semantic RST-based slide authoring
|
|
46
|
+
* Powerful **substep / incremental reveal** system with fine-grained control
|
|
47
|
+
* Multiple output formats:
|
|
48
|
+
* Standard HTML (for direct PDF printing)
|
|
49
|
+
* Substep-expanded HTML (step-by-step handouts)
|
|
50
|
+
* b6plus presentation mode (for projectors / live talks)
|
|
51
|
+
* Embedded Graphviz diagram support (`yographviz` directive)
|
|
52
|
+
* Full offline capability
|
|
53
|
+
* Custom CSS and JavaScript support
|
|
54
|
+
* High-quality print/PDF output
|
|
55
|
+
|
|
56
|
+
Why prezento?
|
|
57
|
+
=============
|
|
58
|
+
|
|
59
|
+
The original `prezentprogramo` was heavily tied to impress.js and had complex indentation requirements. After years of use, I decided to rewrite it from scratch with the following goals:
|
|
60
|
+
|
|
61
|
+
* Better substep semantics (especially for lists and nested content)
|
|
62
|
+
* Modern docutils usage (no deprecated APIs)
|
|
63
|
+
* Cleaner code architecture
|
|
64
|
+
* Switch to **b6plus** — a lightweight and actively maintained presentation library
|
|
65
|
+
* Easier maintenance and future extensibility
|
|
66
|
+
|
|
67
|
+
This version is **not compatible** with original Hovercraft or prezentprogramo RST files, but it offers a much better authoring experience.
|
|
68
|
+
|
|
69
|
+
Sample Slides
|
|
70
|
+
=============
|
|
71
|
+
|
|
72
|
+
You can see real-world examples of prezento in use here:
|
|
73
|
+
|
|
74
|
+
https://github.com/yoosofan/slide
|
|
75
|
+
|
|
76
|
+
Assets Requirement
|
|
77
|
+
==================
|
|
78
|
+
|
|
79
|
+
The generated HTML slides require the following assets:
|
|
80
|
+
|
|
81
|
+
* ``assets/simple.css``
|
|
82
|
+
* ``assets/b6plus.js``
|
|
83
|
+
|
|
84
|
+
**Important**: After generating HTML files, you must have an ``assets/`` folder next to them containing these two files.
|
|
85
|
+
|
|
86
|
+
These assets are taken from the `b6plus <https://github.com/ryanhaviland/b6plus>`_ project. You can update them whenever a newer version is released.
|
|
87
|
+
|
|
88
|
+
Installation
|
|
89
|
+
============
|
|
90
|
+
|
|
91
|
+
From source (recommended during early development):
|
|
92
|
+
|
|
93
|
+
.. code-block:: bash
|
|
94
|
+
|
|
95
|
+
git clone https://github.com/yoosofan/prezento.git
|
|
96
|
+
cd prezento
|
|
97
|
+
pip install -e .
|
|
98
|
+
|
|
99
|
+
Usage
|
|
100
|
+
=====
|
|
101
|
+
|
|
102
|
+
Basic usage:
|
|
103
|
+
|
|
104
|
+
.. code-block:: bash
|
|
105
|
+
|
|
106
|
+
prezento your_slides.rst
|
|
107
|
+
|
|
108
|
+
This will generate three output files in the same directory:
|
|
109
|
+
|
|
110
|
+
* ``your_slides.html`` — Standard version
|
|
111
|
+
* ``your_slides.substep.pdf.html`` — Step-by-step version (good for printing)
|
|
112
|
+
* ``your_slides.presentation.html`` — b6plus interactive version
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
|
|
116
|
+
.. code-block:: bash
|
|
117
|
+
|
|
118
|
+
prezento input.rst -o output.html
|
|
119
|
+
prezento input.rst --no-substep # Skip substep PDF version
|
|
120
|
+
prezento input.rst --no-presentation # Skip b6plus version
|
|
121
|
+
|
|
122
|
+
Project Structure (Development)
|
|
123
|
+
===============================
|
|
124
|
+
|
|
125
|
+
.. code-block:: text
|
|
126
|
+
|
|
127
|
+
prezento/
|
|
128
|
+
├── src/
|
|
129
|
+
│ └── prezento/
|
|
130
|
+
│ ├── __init__.py
|
|
131
|
+
│ └── main.py
|
|
132
|
+
├── docs/
|
|
133
|
+
│ └── dev-history/
|
|
134
|
+
└── tests/
|
|
135
|
+
|
|
136
|
+
Contributing
|
|
137
|
+
============
|
|
138
|
+
|
|
139
|
+
Contributions are welcome! This project is still in active development.
|
|
140
|
+
|
|
141
|
+
If you want to help, please:
|
|
142
|
+
|
|
143
|
+
* Open an issue for bugs or feature requests
|
|
144
|
+
* Submit pull requests for improvements
|
|
145
|
+
* Test with complex slide decks
|
|
146
|
+
|
|
147
|
+
License
|
|
148
|
+
=======
|
|
149
|
+
|
|
150
|
+
This project is licensed under the **GNU General Public License v3.0** (GPLv3).
|
|
151
|
+
|
|
152
|
+
See the `LICENSE` file for the full license text.
|
|
153
|
+
|
|
154
|
+
You are free to use, modify, and distribute this software under the terms of GPLv3.
|
|
155
|
+
|
|
156
|
+
Acknowledgments
|
|
157
|
+
===============
|
|
158
|
+
|
|
159
|
+
* Inspired by `Hovercraft <https://github.com/regebro/hovercraft>`_ and the original `prezentprogramo`
|
|
160
|
+
* Uses `b6plus <https://www.w3.org/Talks/Tools/b6plus/>`_ for presentation mode. https://www.w3.org/Talks/Tools/b6plus/slides.zip
|
|
161
|
+
* Built on top of `docutils <https://docutils.sourceforge.io/>`_
|
|
162
|
+
|
|
163
|
+
Author
|
|
164
|
+
======
|
|
165
|
+
|
|
166
|
+
**Ahmad Yoosofan**
|
|
167
|
+
|
|
168
|
+
- GitHub: https://github.com/yoosofan
|
|
169
|
+
- Slides: https://github.com/yoosofan/slide
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
prezento/__init__.py,sha256=R0nYUZ_dB2zTk45GlbO32EZzzm51HeIA2-TMPp416vo,65
|
|
2
|
+
prezento/main.py,sha256=9P1pR2wKork6O-LZQydzwnJDpGSVp37Er7p1u91iqWk,17898
|
|
3
|
+
prezento-1.0.dist-info/METADATA,sha256=fHY3GX44EQE8ifOzL97BDAUyZUW2D2HfsVnV7Pr2UTo,5319
|
|
4
|
+
prezento-1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
prezento-1.0.dist-info/entry_points.txt,sha256=yEhHWpYCb1LsQWIITZlIP1iAfl4yOOhbjkNoRBx2PSA,48
|
|
6
|
+
prezento-1.0.dist-info/top_level.txt,sha256=YUpBeN7TDqlbSn8_6dyvMOQOotnISas-IVZpn_mwgAQ,9
|
|
7
|
+
prezento-1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prezento
|