Skip to content

Commit 1539b2f

Browse files
authored
feat(cli/convert): add HTML zip output (#472)
Closes #437
1 parent a39a9c2 commit 1539b2f

File tree

3 files changed

+50
-13
lines changed

3 files changed

+50
-13
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919
- Added support for `disable_caching` and `flush_cache` options from Manim, and
2020
also the possibility to configure them through class options.
2121
[#452](https://github.com/jeertmans/manim-slides/pull/452)
22+
- Added `--to=zip` convert format to generate an archive with HTML output
23+
and asset files.
24+
[#470](https://github.com/jeertmans/manim-slides/pull/470)
2225

2326
(unreleased-chore)=
2427
### Chore

manim_slides/convert.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import mimetypes
22
import os
33
import platform
4+
import shutil
45
import subprocess
56
import tempfile
67
import webbrowser
@@ -115,9 +116,9 @@ def load_template(self) -> str:
115116
"""
116117
return ""
117118

118-
def open(self, file: Path) -> Any:
119+
def open(self, file: Path) -> None:
119120
"""Open a file, generated with converter, using appropriate application."""
120-
raise NotImplementedError
121+
open_with_default(file)
121122

122123
@classmethod
123124
def from_string(cls, s: str) -> type["Converter"]:
@@ -126,6 +127,7 @@ def from_string(cls, s: str) -> type["Converter"]:
126127
"html": RevealJS,
127128
"pdf": PDF,
128129
"pptx": PowerPoint,
130+
"zip": HtmlZip,
129131
}[s]
130132

131133

@@ -380,8 +382,8 @@ def load_template(self) -> str:
380382

381383
return resources.files(templates).joinpath("revealjs.html").read_text()
382384

383-
def open(self, file: Path) -> bool:
384-
return webbrowser.open(file.absolute().as_uri())
385+
def open(self, file: Path) -> None:
386+
webbrowser.open(file.absolute().as_uri())
385387

386388
def convert_to(self, dest: Path) -> None:
387389
"""
@@ -452,6 +454,24 @@ def prefix(i: int) -> str:
452454
f.write(content)
453455

454456

457+
class HtmlZip(RevealJS):
458+
def open(self, file: Path) -> None:
459+
super(RevealJS, self).open(file) # Override opening with web browser
460+
461+
def convert_to(self, dest: Path) -> None:
462+
"""
463+
Convert this configuration into a zipped RevealJS HTML presentation, saved to
464+
DEST.
465+
"""
466+
with tempfile.TemporaryDirectory() as directory_name:
467+
directory = Path(directory_name)
468+
469+
html_file = directory / dest.with_suffix(".html").name
470+
471+
super().convert_to(html_file)
472+
shutil.make_archive(str(dest.with_suffix("")), "zip", directory_name)
473+
474+
455475
class FrameIndex(str, Enum):
456476
first = "first"
457477
last = "last"
@@ -462,9 +482,6 @@ class PDF(Converter):
462482
resolution: PositiveFloat = 100.0
463483
model_config = ConfigDict(use_enum_values=True, extra="forbid")
464484

465-
def open(self, file: Path) -> None:
466-
return open_with_default(file)
467-
468485
def convert_to(self, dest: Path) -> None:
469486
"""Convert this configuration into a PDF presentation, saved to DEST."""
470487
images = []
@@ -499,9 +516,6 @@ class PowerPoint(Converter):
499516
poster_frame_image: Optional[FilePath] = None
500517
model_config = ConfigDict(use_enum_values=True, extra="forbid")
501518

502-
def open(self, file: Path) -> None:
503-
return open_with_default(file)
504-
505519
def convert_to(self, dest: Path) -> None:
506520
"""Convert this configuration into a PowerPoint presentation, saved to DEST."""
507521
prs = pptx.Presentation()
@@ -640,7 +654,7 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None:
640654
@click.argument("dest", type=click.Path(dir_okay=False, path_type=Path))
641655
@click.option(
642656
"--to",
643-
type=click.Choice(["auto", "html", "pdf", "pptx"], case_sensitive=False),
657+
type=click.Choice(["auto", "html", "zip", "pdf", "pptx"], case_sensitive=False),
644658
metavar="FORMAT",
645659
default="auto",
646660
show_default=True,

tests/test_convert.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import shutil
12
from enum import EnumMeta
23
from pathlib import Path
34

@@ -17,6 +18,7 @@
1718
ControlsLayout,
1819
Converter,
1920
Display,
21+
HtmlZip,
2022
JsBool,
2123
JsFalse,
2224
JsNull,
@@ -138,7 +140,8 @@ def test_unquoted_enum(enum_type: EnumMeta) -> None:
138140

139141
class TestConverter:
140142
@pytest.mark.parametrize(
141-
("name", "converter"), [("html", RevealJS), ("pdf", PDF), ("pptx", PowerPoint)]
143+
("name", "converter"),
144+
[("html", RevealJS), ("pdf", PDF), ("pptx", PowerPoint), ("zip", HtmlZip)],
142145
)
143146
def test_from_string(self, name: str, converter: type) -> None:
144147
assert Converter.from_string(name) == converter
@@ -150,9 +153,26 @@ def test_revealjs_converter(
150153
RevealJS(presentation_configs=[presentation_config]).convert_to(out_file)
151154
assert out_file.exists()
152155
assert Path(tmp_path / "slides_assets").is_dir()
153-
file_contents = Path(out_file).read_text()
156+
file_contents = out_file.read_text()
154157
assert "manim" in file_contents.casefold()
155158

159+
def test_htmlzip_converter(
160+
self, tmp_path: Path, presentation_config: PresentationConfig
161+
) -> None:
162+
archive = tmp_path / "got.zip"
163+
expected = tmp_path / "expected.html"
164+
got = tmp_path / "got.html"
165+
166+
HtmlZip(presentation_configs=[presentation_config]).convert_to(archive)
167+
RevealJS(presentation_configs=[presentation_config]).convert_to(expected)
168+
169+
shutil.unpack_archive(str(archive))
170+
171+
assert got.exists()
172+
assert expected.exists()
173+
174+
assert got.read_text() == expected.read_text()
175+
156176
@pytest.mark.parametrize("num_presentation_configs", (1, 2))
157177
def test_revealjs_multiple_scenes_converter(
158178
self,

0 commit comments

Comments
 (0)