scrolly 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- scrolly-0.2.2/LICENSE +28 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/PKG-INFO +7 -3
- {scrolly-0.2.0 → scrolly-0.2.2}/README.md +6 -2
- {scrolly-0.2.0 → scrolly-0.2.2}/pyproject.toml +16 -1
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/_cli/_cli.py +69 -10
- scrolly-0.2.2/scrolly/_cli/_errors.py +54 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/__init__.py +46 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_assets.py +43 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_common.py +136 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_dom.py +47 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_elements.py +43 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_slides.py +40 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_snaps.py +44 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_snapshot.py +64 -0
- scrolly-0.2.2/scrolly/_cli/_introspect/_timeline.py +46 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/deck/inference.py +5 -2
- scrolly-0.2.2/scrolly/deck/introspect.py +75 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/deck/parser.py +35 -23
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/deck/validator.py +21 -8
- scrolly-0.2.2/scrolly/errors/__init__.py +59 -0
- scrolly-0.2.2/scrolly/errors/_catalog.py +64 -0
- scrolly-0.2.2/scrolly/errors/_codes.py +54 -0
- scrolly-0.2.2/scrolly/errors/_report.py +41 -0
- scrolly-0.2.2/scrolly/errors/_validation_error.py +96 -0
- scrolly-0.2.2/scrolly/errors/catalog/E001.md +22 -0
- scrolly-0.2.2/scrolly/errors/catalog/E002.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E003.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E004.md +16 -0
- scrolly-0.2.2/scrolly/errors/catalog/E005.md +15 -0
- scrolly-0.2.2/scrolly/errors/catalog/E006.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E007.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E008.md +16 -0
- scrolly-0.2.2/scrolly/errors/catalog/E009.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E010.md +21 -0
- scrolly-0.2.2/scrolly/errors/catalog/E011.md +21 -0
- scrolly-0.2.2/scrolly/errors/catalog/E012.md +20 -0
- scrolly-0.2.2/scrolly/errors/catalog/E101.md +19 -0
- scrolly-0.2.2/scrolly/errors/catalog/E102.md +19 -0
- scrolly-0.2.2/scrolly/errors/catalog/E103.md +22 -0
- scrolly-0.2.2/scrolly/errors/catalog/E201.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E202.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E203.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E204.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E205.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E206.md +19 -0
- scrolly-0.2.2/scrolly/errors/catalog/E207.md +21 -0
- scrolly-0.2.2/scrolly/errors/catalog/E299.md +21 -0
- scrolly-0.2.2/scrolly/errors/catalog/E301.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E302.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E303.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E304.md +19 -0
- scrolly-0.2.2/scrolly/errors/catalog/E305.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E306.md +20 -0
- scrolly-0.2.2/scrolly/errors/catalog/E307.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E308.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E401.md +19 -0
- scrolly-0.2.2/scrolly/errors/catalog/E402.md +21 -0
- scrolly-0.2.2/scrolly/errors/catalog/E403.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E501.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E502.md +20 -0
- scrolly-0.2.2/scrolly/errors/catalog/E503.md +21 -0
- scrolly-0.2.2/scrolly/errors/catalog/E504.md +16 -0
- scrolly-0.2.2/scrolly/errors/catalog/E505.md +19 -0
- scrolly-0.2.2/scrolly/errors/catalog/E601.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E602.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/E603.md +17 -0
- scrolly-0.2.2/scrolly/errors/catalog/E701.md +20 -0
- scrolly-0.2.2/scrolly/errors/catalog/E702.md +18 -0
- scrolly-0.2.2/scrolly/errors/catalog/__init__.py +31 -0
- scrolly-0.2.2/scrolly/pipeline/__init__.py +21 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/pipeline/assets.py +8 -5
- scrolly-0.2.2/scrolly/pipeline/introspect.py +90 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/pipeline/lint.py +2 -3
- scrolly-0.2.2/scrolly/pipeline/loader.py +61 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/pipeline/orchestrator.py +47 -40
- scrolly-0.2.2/scrolly/pipeline/writer.py +52 -0
- scrolly-0.2.2/scrolly/render/__init__.py +6 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/assembler.py +25 -6
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/assets/canvas.css +42 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/assets/canvas.js +236 -123
- scrolly-0.2.0/LICENSE → scrolly-0.2.2/scrolly/render/assets/mermaid-LICENSE +2 -2
- scrolly-0.2.2/scrolly/render/assets/mermaid.min.js +3405 -0
- scrolly-0.2.2/scrolly/render/bundled_assets.py +147 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/registry.py +8 -2
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/rendered.py +1 -1
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/image_sequence.py +65 -51
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/html.py +1 -1
- scrolly-0.2.2/scrolly/slide/introspect.py +429 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/ir/_framework/animated_values.py +91 -8
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/ir/_framework/element.py +79 -27
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/ir/_framework/utils.py +14 -6
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/ir/slide.py +29 -11
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/registry.py +4 -1
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/renderers/slide.py +101 -29
- scrolly-0.2.0/scrolly/errors.py +0 -34
- scrolly-0.2.0/scrolly/pipeline/__init__.py +0 -6
- scrolly-0.2.0/scrolly/pipeline/writer.py +0 -38
- scrolly-0.2.0/scrolly/render/__init__.py +0 -6
- scrolly-0.2.0/scrolly/render/bundled_assets.py +0 -55
- {scrolly-0.2.0 → scrolly-0.2.2}/.gitignore +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/_cli/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/deck/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/deck/model.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/deck/schema.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/pipeline/_bundler.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/fan.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/nav_data.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/templates/index.html.j2 +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/render/zoom_control.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/ir.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/processor.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/_shared.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/html.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/iframe.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/image.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/markdown.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/element_ir/renderers/mermaid.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/ir/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/ir/_framework/__init__.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/processor.py +0 -0
- {scrolly-0.2.0 → scrolly-0.2.2}/scrolly/slide/renderers/__init__.py +0 -0
scrolly-0.2.2/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Bert Pluymers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
----
|
|
24
|
+
|
|
25
|
+
scrolly bundles third-party assets that ship under their own licenses,
|
|
26
|
+
located alongside the asset in the package tree. See for example
|
|
27
|
+
`scrolly/render/assets/mermaid-LICENSE` (MIT — Copyright (c) 2014-2022
|
|
28
|
+
Knut Sveidqvist).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrolly
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation.
|
|
5
5
|
Project-URL: Source, https://github.com/bertpl/scrolly
|
|
6
6
|
Project-URL: Changelog, https://github.com/bertpl/scrolly/blob/main/CHANGELOG.md
|
|
@@ -33,6 +33,10 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
|
|
34
34
|
Compile a JSON5 deck into a self-contained, scrollable 2D-canvas HTML presentation.
|
|
35
35
|
|
|
36
|
+
*Liked by humans; understood by agents.*
|
|
37
|
+
|
|
38
|
+
[](https://github.com/bertpl/scrolly)
|
|
39
|
+
|
|
36
40
|
[](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
|
|
37
41
|
[](https://pypi.org/project/scrolly/)
|
|
38
42
|
[](https://pypi.org/project/scrolly/)
|
|
@@ -53,11 +57,11 @@ uv tool install scrolly
|
|
|
53
57
|
## Quickstart
|
|
54
58
|
|
|
55
59
|
```bash
|
|
56
|
-
scrolly build examples/
|
|
60
|
+
scrolly build examples/stacked-diffs/deck.deck.json --out /tmp/scrolly-out --force
|
|
57
61
|
open /tmp/scrolly-out/index.html
|
|
58
62
|
```
|
|
59
63
|
|
|
60
|
-
See [`examples/
|
|
64
|
+
See [`examples/stacked-diffs/`](examples/stacked-diffs/) for a complete example deck.
|
|
61
65
|
|
|
62
66
|
## Source format
|
|
63
67
|
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Compile a JSON5 deck into a self-contained, scrollable 2D-canvas HTML presentation.
|
|
4
4
|
|
|
5
|
+
*Liked by humans; understood by agents.*
|
|
6
|
+
|
|
7
|
+
[](https://github.com/bertpl/scrolly)
|
|
8
|
+
|
|
5
9
|
[](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
|
|
6
10
|
[](https://pypi.org/project/scrolly/)
|
|
7
11
|
[](https://pypi.org/project/scrolly/)
|
|
@@ -22,11 +26,11 @@ uv tool install scrolly
|
|
|
22
26
|
## Quickstart
|
|
23
27
|
|
|
24
28
|
```bash
|
|
25
|
-
scrolly build examples/
|
|
29
|
+
scrolly build examples/stacked-diffs/deck.deck.json --out /tmp/scrolly-out --force
|
|
26
30
|
open /tmp/scrolly-out/index.html
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
See [`examples/
|
|
33
|
+
See [`examples/stacked-diffs/`](examples/stacked-diffs/) for a complete example deck.
|
|
30
34
|
|
|
31
35
|
## Source format
|
|
32
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "scrolly"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -37,6 +37,17 @@ dev = [
|
|
|
37
37
|
"pytest-cov>=6.0",
|
|
38
38
|
"ruff>=0.14.0",
|
|
39
39
|
"pre-commit>=4.0",
|
|
40
|
+
# Exact pin: gitsvg is pre-1.0 and its output schema can shift
|
|
41
|
+
# between releases. Used to regenerate the stacked-diffs example's
|
|
42
|
+
# gitsvg frames.
|
|
43
|
+
"gitsvg==0.2.2",
|
|
44
|
+
]
|
|
45
|
+
# Optional group for the hero-animation capture pipeline (docs/_gen).
|
|
46
|
+
# Heavy (Playwright ships browser binaries via `make capture-setup`),
|
|
47
|
+
# so it's separate from `dev` — casual contributors don't pay the cost.
|
|
48
|
+
capture = [
|
|
49
|
+
"playwright>=1.40",
|
|
50
|
+
"pillow>=10.0",
|
|
40
51
|
]
|
|
41
52
|
|
|
42
53
|
[project.scripts]
|
|
@@ -66,6 +77,10 @@ select = ["I"]
|
|
|
66
77
|
|
|
67
78
|
[tool.pytest.ini_options]
|
|
68
79
|
testpaths = ["tests/python"]
|
|
80
|
+
# The hero-animation engine lives under docs/_gen (dev-only tooling, not
|
|
81
|
+
# shipped in the wheel); put it on the path so its pure-logic unit tests
|
|
82
|
+
# can import it without the optional `capture` group installed.
|
|
83
|
+
pythonpath = ["docs/_gen"]
|
|
69
84
|
|
|
70
85
|
[tool.coverage.run]
|
|
71
86
|
source = ["scrolly"]
|
|
@@ -6,8 +6,10 @@ import click
|
|
|
6
6
|
from rich.console import Console
|
|
7
7
|
|
|
8
8
|
from scrolly import __version__
|
|
9
|
-
from scrolly.
|
|
10
|
-
from scrolly.
|
|
9
|
+
from scrolly._cli._errors import errors_command
|
|
10
|
+
from scrolly._cli._introspect import introspect
|
|
11
|
+
from scrolly.errors import ScrollyError, ValidationError
|
|
12
|
+
from scrolly.pipeline import build_deck, load_deck
|
|
11
13
|
from scrolly.pipeline.lint import lint_deck
|
|
12
14
|
|
|
13
15
|
_err_console = Console(stderr=True, highlight=False)
|
|
@@ -41,6 +43,14 @@ def cli() -> None:
|
|
|
41
43
|
is_flag=True,
|
|
42
44
|
help="Disable gzip compression of inlined assets.",
|
|
43
45
|
)
|
|
46
|
+
@click.option(
|
|
47
|
+
"--offline",
|
|
48
|
+
is_flag=True,
|
|
49
|
+
help=(
|
|
50
|
+
"Skip the mermaid CDN download and use the wheel-bundled mermaid for "
|
|
51
|
+
"byte-reproducibility. SCROLLY_OFFLINE=1 in the environment is equivalent."
|
|
52
|
+
),
|
|
53
|
+
)
|
|
44
54
|
def build(
|
|
45
55
|
deck_path: Path,
|
|
46
56
|
out_dir: Path,
|
|
@@ -49,6 +59,7 @@ def build(
|
|
|
49
59
|
strict: bool,
|
|
50
60
|
simplified_zoom_control: bool,
|
|
51
61
|
no_compress: bool,
|
|
62
|
+
offline: bool,
|
|
52
63
|
) -> None:
|
|
53
64
|
"""Build a deck into a self-contained HTML presentation."""
|
|
54
65
|
try:
|
|
@@ -59,6 +70,7 @@ def build(
|
|
|
59
70
|
inline=not no_inline,
|
|
60
71
|
simplified_zoom_control=simplified_zoom_control,
|
|
61
72
|
compress=not no_compress,
|
|
73
|
+
offline=offline,
|
|
62
74
|
)
|
|
63
75
|
except ScrollyError as e:
|
|
64
76
|
_err_console.print(f"[red]error:[/red] {e}")
|
|
@@ -72,12 +84,30 @@ def build(
|
|
|
72
84
|
|
|
73
85
|
@cli.command()
|
|
74
86
|
@click.argument("type_name", required=False)
|
|
75
|
-
|
|
76
|
-
"
|
|
87
|
+
@click.option(
|
|
88
|
+
"--list-types",
|
|
89
|
+
"list_types",
|
|
90
|
+
is_flag=True,
|
|
91
|
+
help="Print bare type names one per line (no descriptions) for scripting use.",
|
|
92
|
+
)
|
|
93
|
+
def schema(type_name: str | None, list_types: bool) -> None:
|
|
94
|
+
"""Show source file schemas.
|
|
95
|
+
|
|
96
|
+
\b
|
|
97
|
+
scrolly schema → formatted index of available types
|
|
98
|
+
scrolly schema <type> → JSON Schema for <type>
|
|
99
|
+
scrolly schema --list-types → bare type names, one per line (agent / scripting)
|
|
100
|
+
"""
|
|
77
101
|
from scrolly.deck import deck_source_schema
|
|
78
102
|
from scrolly.slide import registered_ir_types
|
|
79
103
|
|
|
80
104
|
ir_types = registered_ir_types()
|
|
105
|
+
all_type_names = sorted(["deck", *ir_types])
|
|
106
|
+
|
|
107
|
+
if list_types:
|
|
108
|
+
for name in all_type_names:
|
|
109
|
+
click.echo(name)
|
|
110
|
+
return
|
|
81
111
|
|
|
82
112
|
if type_name is None:
|
|
83
113
|
click.echo("Available schemas:\n")
|
|
@@ -92,8 +122,7 @@ def schema(type_name: str | None) -> None:
|
|
|
92
122
|
return
|
|
93
123
|
|
|
94
124
|
if type_name not in ir_types:
|
|
95
|
-
known
|
|
96
|
-
_err_console.print(f"[red]error:[/red] unknown type '{type_name}' (known: {known})")
|
|
125
|
+
_err_console.print(f"[red]error:[/red] unknown type '{type_name}' (known: {', '.join(all_type_names)})")
|
|
97
126
|
sys.exit(1)
|
|
98
127
|
|
|
99
128
|
click.echo(json.dumps(ir_types[type_name].source_schema(), indent=2))
|
|
@@ -102,18 +131,44 @@ def schema(type_name: str | None) -> None:
|
|
|
102
131
|
@cli.command()
|
|
103
132
|
@click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
104
133
|
@click.option("--strict", is_flag=True, help="Enable additional lint checks (e.g. out-of-range keyframes).")
|
|
105
|
-
|
|
134
|
+
@click.option(
|
|
135
|
+
"--json",
|
|
136
|
+
"as_json",
|
|
137
|
+
is_flag=True,
|
|
138
|
+
help='Emit machine-readable JSON instead of text: {"ok": bool, "errors": [...]}.',
|
|
139
|
+
)
|
|
140
|
+
def validate(deck_path: Path, strict: bool, as_json: bool) -> None:
|
|
106
141
|
"""Validate a deck and all its slide sources without building."""
|
|
107
142
|
try:
|
|
108
|
-
deck =
|
|
143
|
+
deck, _ = load_deck(deck_path)
|
|
109
144
|
except ScrollyError as e:
|
|
110
|
-
|
|
145
|
+
if as_json:
|
|
146
|
+
click.echo(json.dumps({"ok": False, "errors": [_error_to_dict(e)]}, indent=2))
|
|
147
|
+
else:
|
|
148
|
+
_err_console.print(f"[red]error:[/red] {e}")
|
|
111
149
|
sys.exit(1)
|
|
112
150
|
|
|
113
151
|
if strict:
|
|
114
152
|
_report_diagnostics(deck)
|
|
115
153
|
|
|
116
|
-
|
|
154
|
+
if as_json:
|
|
155
|
+
click.echo(json.dumps({"ok": True, "errors": []}, indent=2))
|
|
156
|
+
else:
|
|
157
|
+
click.echo(f"Valid: {len(deck.slides)} slides, {len(deck.edges)} edges")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _error_to_dict(err: ScrollyError) -> dict:
|
|
161
|
+
"""Serialise a ``ScrollyError`` for JSON output."""
|
|
162
|
+
if isinstance(err, ValidationError):
|
|
163
|
+
return {
|
|
164
|
+
"code": err.code,
|
|
165
|
+
"message": err.message,
|
|
166
|
+
"file": err.file,
|
|
167
|
+
"line": err.line,
|
|
168
|
+
"field": err.field,
|
|
169
|
+
"suggestion": err.suggestion,
|
|
170
|
+
}
|
|
171
|
+
return {"code": None, "message": str(err)}
|
|
117
172
|
|
|
118
173
|
|
|
119
174
|
def _report_diagnostics(deck) -> None:
|
|
@@ -158,3 +213,7 @@ def init(dir_path: Path) -> None:
|
|
|
158
213
|
(slides_dir / "intro.slide.json").write_text(_INIT_SLIDE)
|
|
159
214
|
|
|
160
215
|
click.echo(f"Created deck in {dir_path}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
cli.add_command(errors_command)
|
|
219
|
+
cli.add_command(introspect)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""``scrolly errors`` — look up registered error codes from the CLI.
|
|
2
|
+
|
|
3
|
+
Three forms, mirroring the progressive-disclosure pattern used by
|
|
4
|
+
``scrolly schema``:
|
|
5
|
+
|
|
6
|
+
* ``scrolly errors`` — formatted index of all codes with summaries.
|
|
7
|
+
* ``scrolly errors <code>`` — long-form catalog entry for ``<code>``.
|
|
8
|
+
* ``scrolly errors --list-codes`` — bare codes, one per line (scripting use).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
|
|
18
|
+
from scrolly.errors import is_registered_code, registered_codes
|
|
19
|
+
from scrolly.errors._catalog import load_body, load_summary
|
|
20
|
+
|
|
21
|
+
_err_console = Console(stderr=True, highlight=False)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.command(name="errors")
|
|
25
|
+
@click.argument("code", required=False)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--list-codes",
|
|
28
|
+
"list_codes",
|
|
29
|
+
is_flag=True,
|
|
30
|
+
help="Print bare codes one per line (no summaries) for scripting use.",
|
|
31
|
+
)
|
|
32
|
+
def errors_command(code: str | None, list_codes: bool) -> None:
|
|
33
|
+
"""Look up registered error codes.
|
|
34
|
+
|
|
35
|
+
\b
|
|
36
|
+
scrolly errors → formatted index of all codes with summaries
|
|
37
|
+
scrolly errors <code> → long-form catalog entry for <code>
|
|
38
|
+
scrolly errors --list-codes → bare codes, one per line (agent / scripting)
|
|
39
|
+
"""
|
|
40
|
+
if list_codes:
|
|
41
|
+
for c in sorted(registered_codes()):
|
|
42
|
+
click.echo(c)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if code is None:
|
|
46
|
+
for c in sorted(registered_codes()):
|
|
47
|
+
click.echo(f" {c:<6} {load_summary(c)}")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
if not is_registered_code(code):
|
|
51
|
+
_err_console.print(f"[red]error:[/red] unknown error code '{code}'")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
click.echo(load_body(code))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""``scrolly introspect <sub>`` — build-time introspection of a resolved deck.
|
|
2
|
+
|
|
3
|
+
Each subcommand surfaces some aspect of what the renderer / browser will
|
|
4
|
+
see, scoped to the agent's biggest blind spot: visual output is unreadable
|
|
5
|
+
to a non-browser consumer, so introspection commands close the loop by
|
|
6
|
+
returning structured JSON describing the deck's resolved state.
|
|
7
|
+
|
|
8
|
+
Common conventions enforced via ``_common.run_introspect_command``:
|
|
9
|
+
|
|
10
|
+
* **JSON-only output**, default to stdout, ``-o PATH`` for a file
|
|
11
|
+
destination.
|
|
12
|
+
* **Validation gate first** — broken decks emit error messages to stderr
|
|
13
|
+
and exit non-zero with no JSON, so consumers never parse stale state.
|
|
14
|
+
* **``--slide <id>``** (repeatable) on subcommands that produce per-slide
|
|
15
|
+
output, restricting the result to the named slides. Unknown ids exit
|
|
16
|
+
non-zero with a clear message.
|
|
17
|
+
|
|
18
|
+
Output format may change before scrolly v1.0 — pin a version when caching
|
|
19
|
+
the schema.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import click
|
|
25
|
+
|
|
26
|
+
from scrolly._cli._introspect._assets import assets_command
|
|
27
|
+
from scrolly._cli._introspect._dom import dom_command
|
|
28
|
+
from scrolly._cli._introspect._elements import elements_command
|
|
29
|
+
from scrolly._cli._introspect._slides import slides_command
|
|
30
|
+
from scrolly._cli._introspect._snaps import snaps_command
|
|
31
|
+
from scrolly._cli._introspect._snapshot import snapshot_command
|
|
32
|
+
from scrolly._cli._introspect._timeline import timeline_command
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.group(name="introspect")
|
|
36
|
+
def introspect() -> None:
|
|
37
|
+
"""Inspect a resolved deck — JSON-only views for downstream consumers."""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
introspect.add_command(slides_command)
|
|
41
|
+
introspect.add_command(elements_command)
|
|
42
|
+
introspect.add_command(snaps_command)
|
|
43
|
+
introspect.add_command(timeline_command)
|
|
44
|
+
introspect.add_command(snapshot_command)
|
|
45
|
+
introspect.add_command(dom_command)
|
|
46
|
+
introspect.add_command(assets_command)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""``scrolly introspect assets`` — per-asset metadata + slide references."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from scrolly._cli._introspect._common import run_introspect_command
|
|
10
|
+
from scrolly.pipeline.introspect import assets_to_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(name="assets")
|
|
14
|
+
@click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
15
|
+
@click.option(
|
|
16
|
+
"--slide",
|
|
17
|
+
"slide_ids",
|
|
18
|
+
multiple=True,
|
|
19
|
+
help="Restrict the asset walk to elements within the named slide(s). Repeatable.",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--output",
|
|
23
|
+
"-o",
|
|
24
|
+
"output_path",
|
|
25
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
26
|
+
help="Write JSON to this file instead of stdout.",
|
|
27
|
+
)
|
|
28
|
+
def assets_command(deck_path: Path, slide_ids: tuple[str, ...], output_path: Path | None) -> None:
|
|
29
|
+
"""Asset table — declared assets, per-slide references, byte sizes, mime types.
|
|
30
|
+
|
|
31
|
+
Walks the resolved slide IRs for ``ImageElement`` / ``ImageSequenceElement``
|
|
32
|
+
references. Each entry reports absolute path, name, size, mime, exists
|
|
33
|
+
flag, and the slides that reference it.
|
|
34
|
+
|
|
35
|
+
Output format may change before scrolly v1.0 — pin a version when
|
|
36
|
+
caching the schema.
|
|
37
|
+
"""
|
|
38
|
+
run_introspect_command(
|
|
39
|
+
deck_path,
|
|
40
|
+
slide_ids=slide_ids,
|
|
41
|
+
output_path=output_path,
|
|
42
|
+
to_json_fn=assets_to_json,
|
|
43
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Shared machinery for the ``scrolly introspect`` subcommands.
|
|
2
|
+
|
|
3
|
+
Every subcommand goes through ``run_introspect_command``, which:
|
|
4
|
+
|
|
5
|
+
1. Loads the deck through the shared validation gate (``load_deck``).
|
|
6
|
+
2. On error, prints to stderr and exits non-zero with no JSON.
|
|
7
|
+
3. If ``slide_ids`` is non-empty, validates each id against the
|
|
8
|
+
resolved slide list and exits with a clear message on unknown ids.
|
|
9
|
+
4. Calls the supplied ``to_json_fn`` to produce the payload.
|
|
10
|
+
5. Writes to ``output_path`` if given, otherwise stdout.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
|
|
23
|
+
from scrolly.deck.model import Deck
|
|
24
|
+
from scrolly.errors import ScrollyError
|
|
25
|
+
from scrolly.pipeline import load_deck
|
|
26
|
+
from scrolly.slide.ir import SlideIR
|
|
27
|
+
|
|
28
|
+
_err_console = Console(stderr=True, highlight=False)
|
|
29
|
+
|
|
30
|
+
ToJsonFn = Callable[[Deck, dict[str, SlideIR], tuple[str, ...] | None], dict]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_introspect_command(
|
|
34
|
+
deck_path: Path,
|
|
35
|
+
slide_ids: tuple[str, ...],
|
|
36
|
+
output_path: Path | None,
|
|
37
|
+
*,
|
|
38
|
+
to_json_fn: ToJsonFn,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Run an introspect subcommand end-to-end: load → filter → serialize → output.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
deck_path: Path to the ``.deck.json`` file.
|
|
44
|
+
slide_ids: Tuple of slide ids to filter to; empty tuple = no filter.
|
|
45
|
+
output_path: Optional file destination; ``None`` writes to stdout.
|
|
46
|
+
to_json_fn: Domain helper that produces the JSON-ready dict.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
SystemExit: Non-zero exit on validation gate failure or unknown
|
|
50
|
+
``--slide`` ids; the error message goes to stderr.
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
deck, slide_irs = load_deck(deck_path)
|
|
54
|
+
except ScrollyError as e:
|
|
55
|
+
_err_console.print(f"[red]error:[/red] {e}")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
if slide_ids:
|
|
59
|
+
known = {s.id for s in deck.slides}
|
|
60
|
+
unknown = [sid for sid in slide_ids if sid not in known]
|
|
61
|
+
if unknown:
|
|
62
|
+
_err_console.print(
|
|
63
|
+
f"[red]error:[/red] unknown slide id(s): {', '.join(unknown)}. Known: {', '.join(sorted(known))}"
|
|
64
|
+
)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
payload = to_json_fn(deck, slide_irs, slide_ids or None)
|
|
68
|
+
rendered = json.dumps(payload, indent=2)
|
|
69
|
+
|
|
70
|
+
if output_path is not None:
|
|
71
|
+
output_path.write_text(rendered, encoding="utf-8")
|
|
72
|
+
else:
|
|
73
|
+
click.echo(rendered)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_snapshot_command(
|
|
77
|
+
deck_path: Path,
|
|
78
|
+
slide_id: str,
|
|
79
|
+
scrolls: tuple[float, ...],
|
|
80
|
+
output_path: Path | None,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Run the snapshot subcommand: load → validate slide_id + scrolls → snapshot → output.
|
|
83
|
+
|
|
84
|
+
Snapshot differs from the other introspect commands in two ways:
|
|
85
|
+
``--slide`` is mandatory and single-valued (scroll positions are
|
|
86
|
+
slide-local, so multi-slide queries are ambiguous), and ``--scroll N``
|
|
87
|
+
is mandatory and repeatable. This helper enforces both invariants
|
|
88
|
+
plus the per-slide scroll-range validation: any ``--scroll`` outside
|
|
89
|
+
``[0, scroll_range]`` for numeric ``scroll_range``, or below 0 for
|
|
90
|
+
``"auto"`` slides, rejects the whole invocation with a clear message
|
|
91
|
+
rather than silently clamping or extrapolating beyond what the
|
|
92
|
+
browser can physically reach.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
deck_path: Path to the ``.deck.json`` file.
|
|
96
|
+
slide_id: Slide to snapshot (mandatory, single).
|
|
97
|
+
scrolls: Tuple of scroll positions (mandatory, non-empty).
|
|
98
|
+
output_path: Optional file destination; ``None`` writes to stdout.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
SystemExit: Non-zero exit on validation gate failure, unknown
|
|
102
|
+
``slide_id``, or any out-of-range scroll value.
|
|
103
|
+
"""
|
|
104
|
+
from scrolly.slide.introspect import snapshot_to_json
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
deck, slide_irs = load_deck(deck_path)
|
|
108
|
+
except ScrollyError as e:
|
|
109
|
+
_err_console.print(f"[red]error:[/red] {e}")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
|
|
112
|
+
known = {s.id for s in deck.slides}
|
|
113
|
+
if slide_id not in known:
|
|
114
|
+
_err_console.print(f"[red]error:[/red] unknown slide id: '{slide_id}'. Known: {', '.join(sorted(known))}")
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
|
|
117
|
+
ir = slide_irs[slide_id]
|
|
118
|
+
scroll_range = ir.scroll_range
|
|
119
|
+
invalid: list[tuple[float, str]] = []
|
|
120
|
+
for scroll in scrolls:
|
|
121
|
+
if scroll < 0:
|
|
122
|
+
invalid.append((scroll, "negative scroll values are never reachable"))
|
|
123
|
+
elif isinstance(scroll_range, (int, float)) and scroll > scroll_range:
|
|
124
|
+
invalid.append((scroll, f"exceeds slide's scroll_range ({scroll_range})"))
|
|
125
|
+
if invalid:
|
|
126
|
+
lines = "\n".join(f" scroll={s}: {reason}" for s, reason in invalid)
|
|
127
|
+
_err_console.print(f"[red]error:[/red] --scroll out-of-range for slide '{slide_id}':\n{lines}")
|
|
128
|
+
sys.exit(1)
|
|
129
|
+
|
|
130
|
+
payload = snapshot_to_json(deck, slide_irs, slide_id, scrolls)
|
|
131
|
+
rendered = json.dumps(payload, indent=2)
|
|
132
|
+
|
|
133
|
+
if output_path is not None:
|
|
134
|
+
output_path.write_text(rendered, encoding="utf-8")
|
|
135
|
+
else:
|
|
136
|
+
click.echo(rendered)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""``scrolly introspect dom`` — rendered HTML + scoped CSS per element."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from scrolly._cli._introspect._common import run_introspect_command
|
|
10
|
+
from scrolly.slide.introspect import dom_to_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(name="dom")
|
|
14
|
+
@click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
15
|
+
@click.option(
|
|
16
|
+
"--slide",
|
|
17
|
+
"slide_ids",
|
|
18
|
+
multiple=True,
|
|
19
|
+
help=(
|
|
20
|
+
"Restrict output to the named slide id(s). Repeatable. Default = all slides — "
|
|
21
|
+
"but unfiltered output can be hundreds of KB; the filter is almost mandatory in practice."
|
|
22
|
+
),
|
|
23
|
+
)
|
|
24
|
+
@click.option(
|
|
25
|
+
"--output",
|
|
26
|
+
"-o",
|
|
27
|
+
"output_path",
|
|
28
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
29
|
+
help="Write JSON to this file instead of stdout.",
|
|
30
|
+
)
|
|
31
|
+
def dom_command(deck_path: Path, slide_ids: tuple[str, ...], output_path: Path | None) -> None:
|
|
32
|
+
"""Rendered HTML + scoped CSS per element, sans deck-level chrome.
|
|
33
|
+
|
|
34
|
+
Answers "what does my config actually become" by running each
|
|
35
|
+
slide's element renderers and surfacing the per-element pieces —
|
|
36
|
+
HTML fragment and CSS rules — directly. No canvas runtime, no
|
|
37
|
+
scrollbar, no edge geometry, no inter-slide chrome.
|
|
38
|
+
|
|
39
|
+
Output format may change before scrolly v1.0 — pin a version when
|
|
40
|
+
caching the schema.
|
|
41
|
+
"""
|
|
42
|
+
run_introspect_command(
|
|
43
|
+
deck_path,
|
|
44
|
+
slide_ids=slide_ids,
|
|
45
|
+
output_path=output_path,
|
|
46
|
+
to_json_fn=dom_to_json,
|
|
47
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""``scrolly introspect elements`` — fully-resolved per-slide element tree."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from scrolly._cli._introspect._common import run_introspect_command
|
|
10
|
+
from scrolly.slide.introspect import element_tree_to_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(name="elements")
|
|
14
|
+
@click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
15
|
+
@click.option(
|
|
16
|
+
"--slide",
|
|
17
|
+
"slide_ids",
|
|
18
|
+
multiple=True,
|
|
19
|
+
help="Restrict output to the named slide id(s). Repeatable. Default = all slides.",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"--output",
|
|
23
|
+
"-o",
|
|
24
|
+
"output_path",
|
|
25
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
26
|
+
help="Write JSON to this file instead of stdout.",
|
|
27
|
+
)
|
|
28
|
+
def elements_command(deck_path: Path, slide_ids: tuple[str, ...], output_path: Path | None) -> None:
|
|
29
|
+
"""Fully-resolved element tree per slide.
|
|
30
|
+
|
|
31
|
+
Defaults are filled in, ``*_file`` fields are inlined, asset paths
|
|
32
|
+
are absolute. Animated properties surface as their keyframe lists;
|
|
33
|
+
static properties surface as their values.
|
|
34
|
+
|
|
35
|
+
Output format may change before scrolly v1.0 — pin a version when
|
|
36
|
+
caching the schema.
|
|
37
|
+
"""
|
|
38
|
+
run_introspect_command(
|
|
39
|
+
deck_path,
|
|
40
|
+
slide_ids=slide_ids,
|
|
41
|
+
output_path=output_path,
|
|
42
|
+
to_json_fn=element_tree_to_json,
|
|
43
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""``scrolly introspect slides`` — deck topology view."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from scrolly._cli._introspect._common import run_introspect_command
|
|
10
|
+
from scrolly.deck.introspect import slides_to_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(name="slides")
|
|
14
|
+
@click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
|
|
15
|
+
@click.option(
|
|
16
|
+
"--output",
|
|
17
|
+
"-o",
|
|
18
|
+
"output_path",
|
|
19
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
20
|
+
help="Write JSON to this file instead of stdout.",
|
|
21
|
+
)
|
|
22
|
+
def slides_command(deck_path: Path, output_path: Path | None) -> None:
|
|
23
|
+
"""Deck topology — slides, edges, groups, geometry.
|
|
24
|
+
|
|
25
|
+
Returns a deck-wide overview: every slide with its grid coord, title,
|
|
26
|
+
resolved scroll_range, element + snap counts; every edge with
|
|
27
|
+
fully-specified sides; every group with members and color.
|
|
28
|
+
|
|
29
|
+
No ``--slide`` filter — the value of this view is the relationships
|
|
30
|
+
between slides, which filtering would destroy.
|
|
31
|
+
|
|
32
|
+
Output format may change before scrolly v1.0 — pin a version when
|
|
33
|
+
caching the schema.
|
|
34
|
+
"""
|
|
35
|
+
run_introspect_command(
|
|
36
|
+
deck_path,
|
|
37
|
+
slide_ids=(),
|
|
38
|
+
output_path=output_path,
|
|
39
|
+
to_json_fn=slides_to_json,
|
|
40
|
+
)
|