mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-19 03:26:17 +08:00
Compare commits
9 Commits
v5.5.1
...
reorganize
Author | SHA1 | Date | |
---|---|---|---|
880cba3064 | |||
fbf5f42f31 | |||
0c6cd67038 | |||
a5412a8df2 | |||
e9480c9bc7 | |||
3c8384a908 | |||
4bf4d38fd8 | |||
f02ef630cf | |||
ae8d5b6aab |
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -96,7 +96,7 @@ jobs:
|
||||
uses: ssciwr/setup-mesa-dist-win@v2
|
||||
|
||||
- name: Run pytest
|
||||
run: uv run --python ${{ matrix.pyversion }} --frozen --extra tests pytest
|
||||
run: uv run --python ${{ matrix.pyversion }} --frozen --group tests --no-dev pytest
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v5
|
||||
|
@ -21,7 +21,7 @@ repos:
|
||||
exclude: poetry.lock
|
||||
args: [--autofix, --trailing-commas]
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.2
|
||||
rev: v0.11.5
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
|
@ -6,13 +6,13 @@ build:
|
||||
apt_packages:
|
||||
- libpango1.0-dev
|
||||
- ffmpeg
|
||||
jobs:
|
||||
post_create_environment:
|
||||
- asdf plugin add uv
|
||||
- asdf install uv latest
|
||||
- asdf global uv latest
|
||||
- UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --group docs --no-dev --no-cache
|
||||
sphinx:
|
||||
builder: html
|
||||
configuration: docs/source/conf.py
|
||||
fail_on_warning: true
|
||||
python:
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -10,6 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
(unreleased)=
|
||||
## [Unreleased](https://github.com/jeertmans/manim-slides/compare/v5.5.1...HEAD)
|
||||
|
||||
(unreleased-chore)=
|
||||
### Chore
|
||||
|
||||
- Moved `docs` and `tests` extras, as well as `dev-dependencies`,
|
||||
inside groups in `dependency-groups`. This could break existing code
|
||||
when using one of those extras, but as they were not part of the public API,
|
||||
we do not consider this to be a **breaking change**.
|
||||
[#542](https://github.com/jeertmans/manim-slides/pull/542)
|
||||
- Moved `manim_slides.docs.manim_slides_directive` to `manim_slides.sphinxext.manim_slides_directive`.
|
||||
This is a **breaking change** because documentation configs have
|
||||
to be updated.
|
||||
[#242](https://github.com/jeertmans/manim-slides/pull/242)
|
||||
|
||||
(v5.5.1)=
|
||||
## [v5.5.1](https://github.com/jeertmans/manim-slides/compare/v5.5.0...v5.5.1)
|
||||
|
||||
@ -24,7 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
Previously, it was not possible to pause HTML slides, which can be very annoying
|
||||
when trying to explain something.
|
||||
[#539](https://github.com/jeertmans/manim-slides/pull/539)
|
||||
|
||||
|
||||
(v5.5.0)=
|
||||
## [v5.5.0](https://github.com/jeertmans/manim-slides/compare/v5.4.2...v5.5.0)
|
||||
|
||||
|
@ -37,7 +37,7 @@ extensions = [
|
||||
"sphinx_copybutton",
|
||||
"sphinx_design",
|
||||
# Custom
|
||||
"manim_slides.docs.manim_slides_directive",
|
||||
"manim_slides.sphinxext.manim_slides_directive",
|
||||
]
|
||||
|
||||
autodoc_typehints = "both"
|
||||
|
@ -32,7 +32,7 @@ and development dependencies. If not already, please install this tool.
|
||||
With uv, installation becomes straightforward:
|
||||
|
||||
```bash
|
||||
uv sync --all-extras
|
||||
uv sync
|
||||
```
|
||||
|
||||
:::{note}
|
||||
|
@ -92,7 +92,7 @@ This will also show the default value for each option.
|
||||
If you want to create your own template, the best is to start from the default one.
|
||||
|
||||
You can either download it from the
|
||||
[template folder](https://github.com/jeertmans/manim-slides/tree/main/manim_slides/templates)
|
||||
[template folder](https://github.com/jeertmans/manim-slides/tree/main/manim_slides/cli/convert/templates)
|
||||
or use the `manim-slides convert --to=FORMAT --show-template` command,
|
||||
where `FORMAT` is one of the supported formats.
|
||||
|
||||
|
@ -10,20 +10,22 @@ The following summarizes the different presentation features Manim Slides offers
|
||||
| :--- | :---: | :---: | :---: | :---: |
|
||||
| Basic navigation through slides | Yes | Yes | Yes | Yes (static image) |
|
||||
| Replay slide | Yes | No | No | N/A |
|
||||
| Pause animation | Yes | No | No | N/A |
|
||||
| Pause animation | Yes | Yes | No | N/A |
|
||||
| Play slide in reverse | Yes | No | No | N/A |
|
||||
| Slide count | Yes | Yes (optional) | Yes (optional) | N/A |
|
||||
| Needs Python with Manim Slides installed | Yes | No | No | No
|
||||
| Requires internet access | No | Yes | No | No |
|
||||
| Requires internet access | No | Depends[^1] | No | No |
|
||||
| Auto. play slides | Yes | Yes | Yes | N/A |
|
||||
| Loops support | Yes | Yes | Yes | N/A |
|
||||
| Fully customizable | No | Yes (`--use-template` option) | No | No |
|
||||
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^1] | None |
|
||||
| Works cross-platforms | Yes | Yes | Partly[^1][^2] | Yes |
|
||||
| Other dependencies | None | A modern web browser | PowerPoint or LibreOffice Impress[^2] | None |
|
||||
| Works cross-platforms | Yes | Yes | Partly[^2][^3] | Yes |
|
||||
:::
|
||||
|
||||
[^1]: If you encounter a problem where slides do not automatically play or loops do not work,
|
||||
[^1]: By default, HTML assets are loaded from the internet, but they can be
|
||||
pre-downloaded and embedded in the HTML file at conversion time.
|
||||
[^2]: If you encounter a problem where slides do not automatically play or loops do not work,
|
||||
please
|
||||
[file an issue on GitHub](https://github.com/jeertmans/manim-slides/issues/new/choose).
|
||||
[^2]: PowerPoint online does not seem to support automatic playing of videos,
|
||||
[^3]: PowerPoint online does not seem to support automatic playing of videos,
|
||||
so you need LibreOffice Impress on Linux platforms.
|
||||
|
@ -4,7 +4,7 @@ This page contains an exhaustive list of all the commands available with `manim-
|
||||
|
||||
|
||||
```{eval-rst}
|
||||
.. click:: manim_slides.__main__:cli
|
||||
.. click:: manim_slides.cli.commands:main
|
||||
:prog: manim-slides
|
||||
:nested: full
|
||||
```
|
||||
|
@ -4,7 +4,7 @@ One of the benefits of the `convert` command is the use of template files.
|
||||
|
||||
Currently, only the HTML export uses one. If not specified, the template
|
||||
will be the one shipped with Manim Slides, see
|
||||
[`manim_slides/templates/revealjs.html`](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/templates/revealjs.html).
|
||||
[`manim_slides/cli/convert/templates/revealjs.html`](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/cli/convert/templates/revealjs.html).
|
||||
|
||||
Because you can actually use your own template with the `--use-template`
|
||||
option, possibilities are infinite!
|
||||
|
@ -30,7 +30,7 @@ manim-slides convert --show-config
|
||||
## Using a Custom Template
|
||||
|
||||
The default template used for HTML conversion can be found on
|
||||
[GitHub](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/templates/revealjs.html)
|
||||
[GitHub](https://github.com/jeertmans/manim-slides/blob/main/manim_slides/cli/convert/templates/revealjs.html)
|
||||
or printed with the `--show-template` option.
|
||||
If you wish to use another template, you can do so with the
|
||||
`--use-template FILE` option.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Manim Slides' Sphinx directive
|
||||
|
||||
```{eval-rst}
|
||||
.. automodule:: manim_slides.docs.manim_slides_directive
|
||||
.. automodule:: manim_slides.sphinxext.manim_slides_directive
|
||||
:members: ManimSlidesDirective
|
||||
```
|
||||
|
@ -1,3 +1,10 @@
|
||||
"""
|
||||
Manim Slides module.
|
||||
|
||||
Submodules are lazily imported, in order to provide a faster import experience
|
||||
in some cases.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
@ -8,9 +15,7 @@ from .__version__ import __version__
|
||||
class Module(ModuleType):
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
if name == "Slide" or name == "ThreeDSlide":
|
||||
module = __import__(
|
||||
"manim_slides.slide", None, None, ["Slide", "ThreeDSlide"]
|
||||
)
|
||||
module = __import__("manim_slides.slide", None, None, [name])
|
||||
return getattr(module, name)
|
||||
elif name == "ManimSlidesMagic":
|
||||
module = __import__(
|
||||
|
@ -1,11 +1,8 @@
|
||||
import json
|
||||
|
||||
import click
|
||||
import requests
|
||||
from click_default_group import DefaultGroup
|
||||
"""Manim Slides' main entrypoint."""
|
||||
|
||||
from .__version__ import __version__
|
||||
from .checkhealth import checkhealth
|
||||
from .cli.commands import main
|
||||
from .convert import convert
|
||||
from .logger import logger
|
||||
from .present import list_scenes, present
|
||||
@ -72,4 +69,4 @@ cli.add_command(render)
|
||||
cli.add_command(wizard)
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
main()
|
||||
|
@ -1 +1,3 @@
|
||||
__version__ = "5.5.1"
|
||||
"""Manim Slides' version."""
|
||||
|
||||
__version__ = "5.4.2"
|
||||
|
72
manim_slides/cli/commands.py
Normal file
72
manim_slides/cli/commands.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""Manim Slides' CLI."""
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
import requests
|
||||
from click_default_group import DefaultGroup
|
||||
|
||||
from ..__version__ import __version__
|
||||
from ..core.logger import logger
|
||||
from .convert.commands import convert
|
||||
from .present.commands import list_scenes, present
|
||||
from .render.commands import render
|
||||
from .wizard.commands import init, wizard
|
||||
|
||||
|
||||
@click.group(cls=DefaultGroup, default="present", default_if_no_args=True)
|
||||
@click.option(
|
||||
"--notify-outdated-version/--silent",
|
||||
" /-S",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Check if a new version of Manim Slides is available.",
|
||||
)
|
||||
@click.version_option(__version__, "-v", "--version")
|
||||
@click.help_option("-h", "--help")
|
||||
def main(notify_outdated_version: bool) -> None:
|
||||
"""
|
||||
Manim Slides command-line utilities.
|
||||
|
||||
If no command is specified, defaults to `present`.
|
||||
"""
|
||||
# Code below is mostly a copy from:
|
||||
# https://github.com/ManimCommunity/manim/blob/main/manim/cli/render/commands.py
|
||||
if notify_outdated_version:
|
||||
manim_info_url = "https://pypi.org/pypi/manim-slides/json"
|
||||
warn_prompt = "Cannot check if latest release of Manim Slides is installed"
|
||||
try:
|
||||
req_info: requests.models.Response = requests.get(manim_info_url, timeout=2)
|
||||
req_info.raise_for_status()
|
||||
stable = req_info.json()["info"]["version"]
|
||||
if stable != __version__:
|
||||
click.echo(
|
||||
"You are using Manim Slides version "
|
||||
+ click.style(f"v{__version__}", fg="red")
|
||||
+ ", but version "
|
||||
+ click.style(f"v{stable}", fg="green")
|
||||
+ " is available."
|
||||
)
|
||||
click.echo(
|
||||
"You should consider upgrading via "
|
||||
+ click.style("pip install -U manim-slides", fg="yellow")
|
||||
)
|
||||
except requests.exceptions.HTTPError:
|
||||
logger.debug(f"HTTP Error: {warn_prompt}")
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.debug(f"Connection Error: {warn_prompt}")
|
||||
except requests.exceptions.Timeout:
|
||||
logger.debug(f"Timed Out: {warn_prompt}")
|
||||
except json.JSONDecodeError:
|
||||
logger.debug(warn_prompt)
|
||||
logger.debug(f"Error decoding JSON from {manim_info_url}")
|
||||
except Exception:
|
||||
logger.debug(f"Something went wrong: {warn_prompt}")
|
||||
|
||||
|
||||
main.add_command(convert)
|
||||
main.add_command(init)
|
||||
main.add_command(list_scenes)
|
||||
main.add_command(present)
|
||||
main.add_command(render)
|
||||
main.add_command(wizard)
|
@ -4,8 +4,9 @@ from typing import Any, Callable
|
||||
import click
|
||||
from click import Context, Parameter
|
||||
|
||||
from .defaults import CONFIG_PATH, FOLDER_PATH
|
||||
from .logger import logger
|
||||
from ..core.config import list_presentation_configs
|
||||
from ..core.defaults import CONFIG_PATH, FOLDER_PATH
|
||||
from ..core.logger import logger
|
||||
|
||||
F = Callable[..., Any]
|
||||
Wrapper = Callable[[F], F]
|
||||
@ -88,6 +89,68 @@ def folder_path_option(function: F) -> F:
|
||||
callback=callback,
|
||||
help="Set slides folder.",
|
||||
show_default=True,
|
||||
is_eager=True, # Needed to expose its value to other callbacks
|
||||
)
|
||||
|
||||
return wrapper(function)
|
||||
|
||||
|
||||
def scenes_argument(function: F) -> F:
|
||||
"""
|
||||
Wrap a function to add a scenes arguments.
|
||||
|
||||
This function assumes that :func:`folder_path_option` is also used
|
||||
on the same decorated function.
|
||||
"""
|
||||
|
||||
def callback(ctx: Context, param: Parameter, value: tuple[str]) -> list[Path]:
|
||||
folder: Path = ctx.params.get("folder")
|
||||
|
||||
presentation_config_paths = list_presentation_configs(folder)
|
||||
scene_names = [path.stem for path in presentation_config_paths]
|
||||
num_scenes = len(scene_names)
|
||||
num_digits = len(str(num_scenes))
|
||||
|
||||
if num_scenes == 0:
|
||||
raise click.UsageError(
|
||||
f"Folder {folder} does not contain "
|
||||
"any valid config file, did you render the animations first?"
|
||||
)
|
||||
|
||||
paths = []
|
||||
|
||||
if value:
|
||||
for scene_name in value:
|
||||
try:
|
||||
i = scene_names.index(scene_name)
|
||||
paths.append(presentation_config_paths[i])
|
||||
except ValueError:
|
||||
raise click.UsageError(
|
||||
f"Could not find scene `{scene_name}` in: "
|
||||
+ ", ".join(scene_names)
|
||||
+ ". Did you make a typo or forgot to render the animations first?"
|
||||
) from None
|
||||
else:
|
||||
click.echo(
|
||||
"Choose at least one or more scenes from "
|
||||
"(enter the corresponding number):\n"
|
||||
+ "\n".join(
|
||||
f"- {i:{num_digits}d}: {name}"
|
||||
for i, name in enumerate(scene_names, start=1)
|
||||
)
|
||||
)
|
||||
continue_prompt = True
|
||||
while continue_prompt:
|
||||
index = click.prompt(
|
||||
"Please enter a value", type=click.IntRange(1, num_scenes)
|
||||
)
|
||||
paths.append(presentation_config_paths[index - 1])
|
||||
continue_prompt = click.confirm(
|
||||
"Do you want to enter an additional scene?"
|
||||
)
|
||||
|
||||
return paths
|
||||
|
||||
wrapper: Wrapper = click.argument("scenes", nargs=-1, callback=callback)
|
||||
|
||||
return wrapper(function)
|
@ -37,14 +37,18 @@ from pydantic_core import CoreSchema, core_schema
|
||||
from pydantic_extra_types.color import Color
|
||||
from tqdm import tqdm
|
||||
|
||||
from ...core.config import PresentationConfig
|
||||
from ...core.logger import logger
|
||||
from ..commons import folder_path_option, scenes_argument, verbosity_option
|
||||
from . import templates
|
||||
from .commons import folder_path_option, verbosity_option
|
||||
from .config import PresentationConfig
|
||||
from .logger import logger
|
||||
from .present import get_scenes_presentation_config
|
||||
|
||||
|
||||
def open_with_default(file: Path) -> None:
|
||||
"""
|
||||
Open a file with the default application.
|
||||
|
||||
:param file: The file to open.
|
||||
"""
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
subprocess.call(("open", str(file)))
|
||||
@ -142,6 +146,7 @@ class Str(str):
|
||||
|
||||
# This fixes pickling issue on Python 3.8
|
||||
__reduce_ex__ = str.__reduce_ex__
|
||||
# TODO: do we still need this?
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(
|
||||
@ -547,6 +552,11 @@ class RevealJS(Converter):
|
||||
return resources.files(templates).joinpath("revealjs.html").read_text()
|
||||
|
||||
def open(self, file: Path) -> None:
|
||||
"""
|
||||
Open the HTML file inside a web browser.
|
||||
|
||||
:param path: The path to the HTML file.
|
||||
"""
|
||||
webbrowser.open(file.absolute().as_uri())
|
||||
|
||||
def convert_to(self, dest: Path) -> None: # noqa: C901
|
||||
@ -910,7 +920,7 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("scenes", nargs=-1)
|
||||
@scenes_argument
|
||||
@folder_path_option
|
||||
@click.argument("dest", type=click.Path(dir_okay=False, path_type=Path))
|
||||
@click.option(
|
||||
@ -960,7 +970,7 @@ def show_template_option(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@show_config_options
|
||||
@verbosity_option
|
||||
def convert(
|
||||
scenes: list[str],
|
||||
scenes: list[Path],
|
||||
folder: Path,
|
||||
dest: Path,
|
||||
to: str,
|
||||
@ -971,7 +981,7 @@ def convert(
|
||||
one_file: bool,
|
||||
) -> None:
|
||||
"""Convert SCENE(s) into a given format and writes the result in DEST."""
|
||||
presentation_configs = get_scenes_presentation_config(scenes, folder)
|
||||
presentation_configs = [PresentationConfig.from_file(scene) for scene in scenes]
|
||||
|
||||
try:
|
||||
if to == "auto":
|
1
manim_slides/cli/convert/templates/__init__.py
Normal file
1
manim_slides/cli/convert/templates/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Manim Slides conversion templates."""
|
1
manim_slides/cli/present/__init__.py
Normal file
1
manim_slides/cli/present/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Manim Slides' presentation commands."""
|
@ -7,9 +7,14 @@ import click
|
||||
from click import Context, Parameter
|
||||
from pydantic import ValidationError
|
||||
|
||||
from ..commons import config_path_option, folder_path_option, verbosity_option
|
||||
from ..config import Config, PresentationConfig
|
||||
from ..logger import logger
|
||||
from ...core.config import Config, PresentationConfig, list_presentation_configs
|
||||
from ...core.logger import logger
|
||||
from ..commons import (
|
||||
config_path_option,
|
||||
folder_path_option,
|
||||
scenes_argument,
|
||||
verbosity_option,
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
@ -18,8 +23,10 @@ from ..logger import logger
|
||||
@verbosity_option
|
||||
def list_scenes(folder: Path) -> None:
|
||||
"""List available scenes."""
|
||||
for i, scene in enumerate(_list_scenes(folder), start=1):
|
||||
click.secho(f"{i}: {scene}", fg="green")
|
||||
scene_names = [path.stem for path in list_presentation_configs(folder)]
|
||||
num_digits = len(str(len(scene_names)))
|
||||
for i, scene_name in enumerate(scene_names, start=1):
|
||||
click.secho(f"{i:{num_digits}d}: {scene_name}", fg="green")
|
||||
|
||||
|
||||
def _list_scenes(folder: Path) -> list[str]:
|
||||
@ -130,7 +137,7 @@ def start_at_callback(
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("scenes", nargs=-1)
|
||||
@scenes_argument
|
||||
@config_path_option
|
||||
@folder_path_option
|
||||
@click.option("--start-paused", is_flag=True, help="Start paused.")
|
||||
@ -276,7 +283,7 @@ def present( # noqa: C901
|
||||
if skip_all:
|
||||
exit_after_last_slide = True
|
||||
|
||||
presentation_configs = get_scenes_presentation_config(scenes, folder)
|
||||
presentation_configs = [PresentationConfig.from_file(path) for path in scenes]
|
||||
|
||||
if config_path.exists():
|
||||
try:
|
@ -1,14 +1,4 @@
|
||||
"""
|
||||
Alias command to either
|
||||
``manim render [OPTIONS] [ARGS]...`` or
|
||||
``manimgl -w [OPTIONS] [ARGS]...``.
|
||||
|
||||
This is especially useful for two reasons:
|
||||
|
||||
1. You can are sure to execute the rendering command with the same Python environment
|
||||
as for ``manim-slides``.
|
||||
2. You can pass options to the config.
|
||||
"""
|
||||
"""Manim Slides' rendering commands."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
@ -44,10 +34,22 @@ def render(ce: bool, gl: bool, args: tuple[str, ...]) -> None:
|
||||
|
||||
Use ``manim-slides render --help`` to see help information for
|
||||
a specific renderer.
|
||||
|
||||
Alias command to either
|
||||
``manim render [OPTIONS] [ARGS]...`` or
|
||||
``manimgl [OPTIONS] [ARGS]...``.
|
||||
|
||||
This is especially useful for two reasons:
|
||||
|
||||
1. You can are sure to execute the rendering command with the same Python environment
|
||||
as for ``manim-slides``.
|
||||
2. You can pass options to the config.
|
||||
"""
|
||||
if ce and gl:
|
||||
raise click.UsageError("You cannot specify both --CE and --GL renderers.")
|
||||
if gl:
|
||||
subprocess.run([sys.executable, "-m", "manimlib", "-w", *args])
|
||||
else:
|
||||
subprocess.run([sys.executable, "-m", "manim", "render", *args])
|
||||
from manim.cli.render.commands import render as render_ce
|
||||
|
||||
render_ce(args, standalone_mode=False)
|
1
manim_slides/cli/wizard/__init__.py
Normal file
1
manim_slides/cli/wizard/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Manim Slides' wizard."""
|
@ -3,10 +3,9 @@ from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
from ...core.config import Config
|
||||
from ...core.logger import logger
|
||||
from ..commons import config_options, verbosity_option
|
||||
from ..config import Config
|
||||
from ..defaults import CONFIG_PATH
|
||||
from ..logger import logger
|
||||
|
||||
|
||||
@click.command()
|
||||
@ -37,7 +36,7 @@ def _init(
|
||||
mode.
|
||||
"""
|
||||
if config_path.exists():
|
||||
click.secho(f"The `{CONFIG_PATH}` configuration file exists")
|
||||
logger.debug(f"The `{config_path}` configuration file exists")
|
||||
|
||||
if not force and not merge:
|
||||
choice = click.prompt(
|
||||
@ -57,7 +56,7 @@ def _init(
|
||||
if force:
|
||||
logger.debug(f"Overwriting `{config_path}` if exists")
|
||||
elif merge:
|
||||
logger.debug("Merging new config into `{config_path}`")
|
||||
logger.debug(f"Merging new config into `{config_path}`")
|
||||
|
||||
if not skip_interactive:
|
||||
if config_path.exists():
|
||||
@ -82,4 +81,4 @@ def _init(
|
||||
|
||||
config.to_file(config_path)
|
||||
|
||||
click.secho(f"Configuration file successfully saved to `{config_path}`")
|
||||
logger.debug(f"Configuration file successfully saved to `{config_path}`")
|
@ -1,3 +1,5 @@
|
||||
"""Manim Slides' configuration tools."""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from functools import wraps
|
||||
@ -13,6 +15,7 @@ from pydantic import (
|
||||
FilePath,
|
||||
PositiveInt,
|
||||
PrivateAttr,
|
||||
ValidationError,
|
||||
conset,
|
||||
field_serializer,
|
||||
field_validator,
|
||||
@ -26,28 +29,54 @@ Receiver = Callable[..., Any]
|
||||
|
||||
|
||||
class Signal(BaseModel): # type: ignore[misc]
|
||||
__receivers: list[Receiver] = PrivateAttr(default_factory=list)
|
||||
"""Signal that notifies a list of receivers when it is emitted."""
|
||||
|
||||
__receivers: set[Receiver] = PrivateAttr(default_factory=set)
|
||||
|
||||
def connect(self, receiver: Receiver) -> None:
|
||||
self.__receivers.append(receiver)
|
||||
"""
|
||||
Connect a receiver to this signal.
|
||||
|
||||
This is a no-op if the receiver was already connected to this signal.
|
||||
|
||||
:param receiver: The receiver to connect.
|
||||
"""
|
||||
self.__receivers.add(receiver)
|
||||
|
||||
def disconnect(self, receiver: Receiver) -> None:
|
||||
self.__receivers.remove(receiver)
|
||||
"""
|
||||
Disconnect a receiver from this signal.
|
||||
|
||||
This is a no-op if the receiver was not connected to this signal.
|
||||
|
||||
:param receiver: The receiver to disconnect.
|
||||
"""
|
||||
self.__receivers.discard(receiver)
|
||||
|
||||
def emit(self, *args: Any) -> None:
|
||||
"""
|
||||
Emit this signal and call each of the attached receivers.
|
||||
|
||||
:param args: Positional arguments passed to each receiver.
|
||||
"""
|
||||
for receiver in self.__receivers:
|
||||
receiver(*args)
|
||||
|
||||
|
||||
def key_id(name: str) -> PositiveInt:
|
||||
"""Avoid importing Qt too early."""
|
||||
from qtpy.QtCore import Qt
|
||||
"""
|
||||
Return the id corresponding to the given key name.
|
||||
|
||||
:param str: The name of the key, e.g., 'Q'.
|
||||
:return: The corresponding id.
|
||||
"""
|
||||
from qtpy.QtCore import Qt # Avoid importing Qt too early."""
|
||||
|
||||
return getattr(Qt, f"Key_{name}")
|
||||
|
||||
|
||||
class Key(BaseModel): # type: ignore[misc]
|
||||
"""Represents a list of key codes, with optionally a name."""
|
||||
"""Represent a list of key codes, with optionally a name."""
|
||||
|
||||
ids: conset(PositiveInt, min_length=1) # type: ignore[valid-type]
|
||||
name: Optional[str] = None
|
||||
@ -58,6 +87,7 @@ class Key(BaseModel): # type: ignore[misc]
|
||||
self.ids = set(ids)
|
||||
|
||||
def match(self, key_id: int) -> bool:
|
||||
"""Return whether a given key id matches this key."""
|
||||
m = key_id in self.ids
|
||||
|
||||
if m:
|
||||
@ -135,6 +165,7 @@ class Config(BaseModel): # type: ignore[misc]
|
||||
"""General Manim Slides config."""
|
||||
|
||||
keys: Keys = Field(default_factory=Keys)
|
||||
"""The key mapping."""
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> "Config":
|
||||
@ -142,11 +173,16 @@ class Config(BaseModel): # type: ignore[misc]
|
||||
return cls.model_validate(rtoml.load(path)) # type: ignore
|
||||
|
||||
def to_file(self, path: Path) -> None:
|
||||
"""Dump the configuration to a file."""
|
||||
"""Dump this configuration to a file."""
|
||||
rtoml.dump(self.model_dump(), path, pretty=True)
|
||||
|
||||
def merge_with(self, other: "Config") -> "Config":
|
||||
"""Merge with another config."""
|
||||
"""
|
||||
Merge with another config.
|
||||
|
||||
:param other: The other config to be merged with.
|
||||
:return: This config, updated.
|
||||
"""
|
||||
self.keys = self.keys.merge_with(other.keys)
|
||||
return self
|
||||
|
||||
@ -155,11 +191,17 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
||||
"""Base class for slide config."""
|
||||
|
||||
loop: bool = False
|
||||
"""Whether this slide should loop."""
|
||||
auto_next: bool = False
|
||||
"""Whether this slide is skipped upon completion."""
|
||||
playback_rate: float = 1.0
|
||||
"""The speed at which the animation is played (1.0 is normal)."""
|
||||
reversed_playback_rate: float = 1.0
|
||||
"""The speed at which the reversed animation is played."""
|
||||
notes: str = ""
|
||||
"""The notes attached to this slide."""
|
||||
dedent_notes: bool = True
|
||||
"""Whether to automatically remove any leading indentation in the notes."""
|
||||
skip_animations: bool = False
|
||||
src: Optional[FilePath] = None
|
||||
|
||||
@ -173,7 +215,11 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
||||
The wrapped function must follow two criteria:
|
||||
- its last parameter must be ``**kwargs`` (or equivalent);
|
||||
- and its second last parameter must be ``<arg_name>``.
|
||||
|
||||
:param arg_name: The name of the argument.
|
||||
:return: The wrapped function.
|
||||
"""
|
||||
# TODO: improve docs and (maybe) type-hints too
|
||||
|
||||
def _wrapper_(fun: Callable[..., Any]) -> Callable[..., Any]:
|
||||
@wraps(fun)
|
||||
@ -209,6 +255,11 @@ class BaseSlideConfig(BaseModel): # type: ignore
|
||||
def apply_dedent_notes(
|
||||
self,
|
||||
) -> "BaseSlideConfig":
|
||||
"""
|
||||
Remove indentation from notes, if specified.
|
||||
|
||||
:return: The config, optionally modified.
|
||||
"""
|
||||
if self.dedent_notes:
|
||||
self.notes = dedent(self.notes)
|
||||
|
||||
@ -219,7 +270,9 @@ class PreSlideConfig(BaseSlideConfig):
|
||||
"""Slide config to be used prior to rendering."""
|
||||
|
||||
start_animation: int
|
||||
"""The index of the first animation."""
|
||||
end_animation: int
|
||||
"""The index after the last animation."""
|
||||
|
||||
@classmethod
|
||||
def from_base_slide_config_and_animation_indices(
|
||||
@ -228,6 +281,13 @@ class PreSlideConfig(BaseSlideConfig):
|
||||
start_animation: int,
|
||||
end_animation: int,
|
||||
) -> "PreSlideConfig":
|
||||
"""
|
||||
Create a config from a base config and animation indices.
|
||||
|
||||
:param base_slide_config: The base config.
|
||||
:param start_animation: The index of the first animation.
|
||||
:param end_animation: The index after the last animation.
|
||||
"""
|
||||
return cls(
|
||||
start_animation=start_animation,
|
||||
end_animation=end_animation,
|
||||
@ -237,6 +297,12 @@ class PreSlideConfig(BaseSlideConfig):
|
||||
@field_validator("start_animation", "end_animation")
|
||||
@classmethod
|
||||
def index_is_posint(cls, v: int) -> int:
|
||||
"""
|
||||
Validate that animation indices are positive integers.
|
||||
|
||||
:param v: An animation index.
|
||||
:return: The animation index, if valid.
|
||||
"""
|
||||
if v < 0:
|
||||
raise ValueError("Animation index (start or end) cannot be negative")
|
||||
return v
|
||||
@ -245,7 +311,11 @@ class PreSlideConfig(BaseSlideConfig):
|
||||
def start_animation_is_before_end(
|
||||
self,
|
||||
) -> "PreSlideConfig":
|
||||
if self.start_animation > self.end_animation:
|
||||
"""
|
||||
Validate that start and end animation indices satisfy 'start < end'.
|
||||
|
||||
:return: The config, if indices are valid.
|
||||
"""
|
||||
raise ValueError(
|
||||
"Start animation index must be strictly lower than end animation index"
|
||||
)
|
||||
@ -279,7 +349,9 @@ class SlideConfig(BaseSlideConfig):
|
||||
"""Slide config to be used after rendering."""
|
||||
|
||||
file: FilePath
|
||||
"""The file containing the animation."""
|
||||
rev_file: FilePath
|
||||
"""The file containing the reversed animation."""
|
||||
|
||||
@classmethod
|
||||
def from_pre_slide_config_and_files(
|
||||
@ -289,13 +361,22 @@ class SlideConfig(BaseSlideConfig):
|
||||
|
||||
|
||||
class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
"""Presentation config that contains all necessary information for a presentation."""
|
||||
|
||||
slides: list[SlideConfig] = Field(min_length=1)
|
||||
"""The non-empty list of slide configs."""
|
||||
resolution: tuple[PositiveInt, PositiveInt] = (1920, 1080)
|
||||
"""The resolution of the animation files."""
|
||||
background_color: Color = "black"
|
||||
"""The background color of the animation files."""
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, path: Path) -> "PresentationConfig":
|
||||
"""Read a presentation configuration from a file."""
|
||||
"""
|
||||
Read a presentation configuration from a file.
|
||||
|
||||
:param path: The path where the config is read from.
|
||||
"""
|
||||
with open(path) as f:
|
||||
obj = json.load(f)
|
||||
|
||||
@ -312,7 +393,11 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
return cls.model_validate(obj) # type: ignore
|
||||
|
||||
def to_file(self, path: Path) -> None:
|
||||
"""Dump the presentation configuration to a file."""
|
||||
"""
|
||||
Dump the presentation configuration to a file.
|
||||
|
||||
:param path: The path to save this config.
|
||||
"""
|
||||
with open(path, "w") as f:
|
||||
f.write(self.model_dump_json(indent=2))
|
||||
|
||||
@ -323,7 +408,14 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
include_reversed: bool = True,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
"""Copy the files to a given directory."""
|
||||
"""
|
||||
Copy the files to a given directory and return the corresponding configuration.
|
||||
|
||||
:param folder: The folder that will contain the animation files.
|
||||
:param use_cached: Whether caching should be used to avoid copies when possible.
|
||||
:param include_reversed: Whether to also copy reversed animation to the folder.
|
||||
:param prefix: Optional prefix added to each file name.
|
||||
"""
|
||||
for slide_config in self.slides:
|
||||
file = slide_config.file
|
||||
rev_file = slide_config.rev_file
|
||||
@ -335,4 +427,6 @@ class PresentationConfig(BaseModel): # type: ignore[misc]
|
||||
shutil.copy(file, dest)
|
||||
|
||||
if include_reversed and (not use_cached or not rev_dest.exists()):
|
||||
# TODO: if include_reversed is False, then rev_dev will likely not exist
|
||||
# and this will cause an issue when decoding.
|
||||
shutil.copy(rev_file, rev_dest)
|
@ -1,4 +1,8 @@
|
||||
"""Manim Slides' defaults."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
FOLDER_PATH: Path = Path("./slides")
|
||||
"""Folder where slides are stored."""
|
||||
CONFIG_PATH: Path = Path(".manim-slides.toml")
|
||||
"""Path to local Manim Slides config."""
|
@ -31,7 +31,11 @@ HIGHLIGHTED_KEYWORDS = [ # these keywords are highlighted specially
|
||||
|
||||
|
||||
def make_logger() -> logging.Logger:
|
||||
"""Make a logger similar to the one used by Manim."""
|
||||
"""
|
||||
Make a logger similar to the one used by Manim.
|
||||
|
||||
:return: The logger instance.
|
||||
"""
|
||||
RichHandler.KEYWORDS = HIGHLIGHTED_KEYWORDS
|
||||
rich_handler = RichHandler(
|
||||
show_time=True,
|
||||
@ -47,6 +51,5 @@ def make_logger() -> logging.Logger:
|
||||
return logger
|
||||
|
||||
|
||||
make_logger()
|
||||
|
||||
logger = logging.getLogger("manim-slides")
|
||||
logger = make_logger()
|
||||
"""The logger instance used across this project."""
|
@ -116,7 +116,7 @@ class ManimSlidesMagic(Magics): # type: ignore
|
||||
file) will be moved relative to the video locations. Use-cases include building
|
||||
documentation with Sphinx and JupyterBook. See also the
|
||||
:mod:`Manim Slides directive for Sphinx
|
||||
<manim_slides.docs.manim_slides_directive>`.
|
||||
<manim_slides.sphinxext.manim_slides_directive>`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
@ -1,10 +1,12 @@
|
||||
__all__ = [
|
||||
"""Slides module with logic to either import ManimCE or ManimGL."""
|
||||
|
||||
__all__ = (
|
||||
"API_NAME",
|
||||
"MANIM",
|
||||
"MANIMGL",
|
||||
"Slide",
|
||||
"ThreeDSlide",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
import os
|
||||
@ -14,10 +16,10 @@ import sys
|
||||
class ManimApiNotFoundError(ImportError):
|
||||
"""Error raised if specified manim API could be imported."""
|
||||
|
||||
_msg = "Could not import the specified manim API"
|
||||
_msg = "Could not import the specified manim API: `{api}`."
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(self._msg)
|
||||
def __init__(self, api: str) -> None:
|
||||
super().__init__(self._msg.format(api=api))
|
||||
|
||||
|
||||
API_NAMES = {
|
||||
@ -26,9 +28,12 @@ API_NAMES = {
|
||||
"manimlib": "manimlib",
|
||||
"manimgl": "manimlib",
|
||||
}
|
||||
"""Allowed values for API."""
|
||||
|
||||
MANIM_API: str = "MANIM_API"
|
||||
"""API environ variable name."""
|
||||
FORCE_MANIM_API: str = "FORCE_" + MANIM_API
|
||||
"""FORCE API environ variable name."""
|
||||
|
||||
API: str = os.environ.get(MANIM_API, "manim").lower()
|
||||
|
||||
@ -53,11 +58,14 @@ if MANIM:
|
||||
try:
|
||||
from .manim import Slide, ThreeDSlide
|
||||
except ImportError as e:
|
||||
raise ManimApiNotFoundError from e
|
||||
raise ManimApiNotFoundError("manim") from e
|
||||
elif MANIMGL:
|
||||
try:
|
||||
from .manimlib import Slide, ThreeDSlide
|
||||
except ImportError as e:
|
||||
raise ManimApiNotFoundError from e
|
||||
raise ManimApiNotFoundError("manimlib") from e
|
||||
else:
|
||||
raise ManimApiNotFoundError
|
||||
raise ValueError(
|
||||
"This error should never occur. "
|
||||
"Please report an issue on GitHub if you encounter it."
|
||||
)
|
||||
|
@ -1,6 +1,8 @@
|
||||
"""Base class for the Slide class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = ["BaseSlide"]
|
||||
__all__ = ("BaseSlide",)
|
||||
|
||||
import platform
|
||||
import shutil
|
||||
@ -15,10 +17,15 @@ from typing import (
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
from ..config import BaseSlideConfig, PresentationConfig, PreSlideConfig, SlideConfig
|
||||
from ..defaults import FOLDER_PATH
|
||||
from ..logger import logger
|
||||
from ..utils import concatenate_video_files, merge_basenames, reverse_video_file
|
||||
from ..core.config import (
|
||||
BaseSlideConfig,
|
||||
PresentationConfig,
|
||||
PreSlideConfig,
|
||||
SlideConfig,
|
||||
)
|
||||
from ..core.defaults import FOLDER_PATH
|
||||
from ..core.logger import logger
|
||||
from ..core.utils import concatenate_video_files, merge_basenames, reverse_video_file
|
||||
from . import MANIM
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""Manim's implementation of the Slide class."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
@ -5,7 +7,7 @@ from manim import Scene, ThreeDScene, config
|
||||
from manim.renderer.opengl_renderer import OpenGLRenderer
|
||||
from manim.utils.color import rgba_to_color
|
||||
|
||||
from ..config import BaseSlideConfig
|
||||
from ..core.config import BaseSlideConfig
|
||||
from .base import BaseSlide
|
||||
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""ManimGL's implementation of the Slide class."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Optional
|
||||
|
||||
|
0
manim_slides/sphinxext/__init__.py
Normal file
0
manim_slides/sphinxext/__init__.py
Normal file
@ -40,7 +40,7 @@ First, you must include the directive in the Sphinx configuration file:
|
||||
|
||||
extensions = [
|
||||
# ...
|
||||
"manim_slides.docs.manim_slides_directive",
|
||||
"manim_slides.sphinxext.manim_slides_directive",
|
||||
]
|
||||
|
||||
Its basic usage that allows processing **inline content**
|
@ -2,6 +2,39 @@
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["hatchling", "hatch-fancy-pypi-readme"]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
{include-group = "docs"},
|
||||
{include-group = "tests"},
|
||||
"bump-my-version>=0.20.3",
|
||||
"pre-commit>=3.5.0",
|
||||
]
|
||||
docs = [
|
||||
"manim-slides[magic,manim,pyqt6,sphinx-directive]",
|
||||
"furo>=2023.5.20",
|
||||
"ipykernel>=6.25.1",
|
||||
"myst-parser>=2.0.0",
|
||||
"nbsphinx>=0.9.2",
|
||||
"pandoc>=2.3",
|
||||
"pygments<2.19", # See: https://github.com/ManimCommunity/manim/issues/4104
|
||||
"sphinx>=7.0.1",
|
||||
"sphinxcontrib-programoutput>=0.18",
|
||||
"sphinx-design>=0.6.1",
|
||||
"sphinx-click>=4.4.0",
|
||||
"sphinx-copybutton>=0.5.1",
|
||||
"sphinxext-opengraph>=0.7.5",
|
||||
]
|
||||
tests = [
|
||||
"importlib-metadata>=8.6.1;python_version<'3.10'",
|
||||
"manim-slides[full,manimgl,pyqt6,pyside6,sphinx-directive]",
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-env>=0.8.2",
|
||||
"pytest-missing-modules>=0.1.0",
|
||||
"pytest-qt>=4.2.0",
|
||||
"setuptools>=73.0.1",
|
||||
]
|
||||
|
||||
[project]
|
||||
authors = [{name = "Jérome Eertmans", email = "jeertmans@icloud.com"}]
|
||||
classifiers = [
|
||||
@ -42,21 +75,6 @@ name = "manim-slides"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
[project.optional-dependencies]
|
||||
docs = [
|
||||
"manim-slides[magic,manim,pyqt6,sphinx-directive]",
|
||||
"furo>=2023.5.20",
|
||||
"ipykernel>=6.25.1",
|
||||
"myst-parser>=2.0.0",
|
||||
"nbsphinx>=0.9.2",
|
||||
"pandoc>=2.3",
|
||||
"pygments<2.19", # See: https://github.com/ManimCommunity/manim/issues/4104
|
||||
"sphinx>=7.0.1",
|
||||
"sphinxcontrib-programoutput>=0.18",
|
||||
"sphinx-design>=0.6.1",
|
||||
"sphinx-click>=4.4.0",
|
||||
"sphinx-copybutton>=0.5.1",
|
||||
"sphinxext-opengraph>=0.7.5",
|
||||
]
|
||||
full = [
|
||||
"manim-slides[magic,manim,sphinx-directive]",
|
||||
]
|
||||
@ -68,18 +86,9 @@ pyqt6-full = ["manim-slides[full,pyqt6]"]
|
||||
pyside6 = ["pyside6>=6.6.1,!=6.8.1.1"]
|
||||
pyside6-full = ["manim-slides[full,pyside6]"]
|
||||
sphinx-directive = ["docutils>=0.20.1", "manim-slides[manim]"]
|
||||
tests = [
|
||||
"importlib-metadata>=8.6.1;python_version<'3.10'",
|
||||
"manim-slides[full,manimgl,pyqt6,pyside6,sphinx-directive]",
|
||||
"pytest>=7.4.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-env>=0.8.2",
|
||||
"pytest-missing-modules>=0.1.0",
|
||||
"pytest-qt>=4.2.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
manim-slides = "manim_slides.__main__:cli"
|
||||
manim-slides = "manim_slides.cli.commands:main"
|
||||
|
||||
[project.urls]
|
||||
Changelog = "https://github.com/jeertmans/manim-slides/releases"
|
||||
@ -199,10 +208,9 @@ filterwarnings = [
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
extend-exclude = ["manim_slides/resources.py"]
|
||||
extend-exclude = ["manim_slides/cli/resources.py"]
|
||||
extend-include = ["*.ipynb"]
|
||||
line-length = 88
|
||||
target-version = "py39"
|
||||
|
||||
[tool.ruff.lint]
|
||||
extend-ignore = [
|
||||
@ -225,10 +233,3 @@ isort = {known-first-party = ["manim_slides", "tests"]}
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"docs/source/reference/magic_example.ipynb" = ["F403", "F405"]
|
||||
"tests/test_slide.py" = ["N801"]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"bump-my-version>=0.20.3",
|
||||
"pre-commit>=3.5.0",
|
||||
"setuptools>=73.0.1",
|
||||
]
|
||||
|
155
uv.lock
generated
155
uv.lock
generated
@ -1549,27 +1549,6 @@ dependencies = [
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
docs = [
|
||||
{ name = "docutils" },
|
||||
{ name = "furo" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "ipython", version = "8.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "manim" },
|
||||
{ name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "myst-parser", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "pandoc" },
|
||||
{ name = "pygments" },
|
||||
{ name = "pyqt6" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "sphinx-click" },
|
||||
{ name = "sphinx-copybutton" },
|
||||
{ name = "sphinx-design" },
|
||||
{ name = "sphinxcontrib-programoutput" },
|
||||
{ name = "sphinxext-opengraph" },
|
||||
]
|
||||
full = [
|
||||
{ name = "docutils" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
@ -1611,26 +1590,59 @@ sphinx-directive = [
|
||||
{ name = "docutils" },
|
||||
{ name = "manim" },
|
||||
]
|
||||
tests = [
|
||||
{ name = "docutils" },
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "bump-my-version" },
|
||||
{ name = "furo" },
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
|
||||
{ name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "ipython", version = "8.31.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "manim" },
|
||||
{ name = "manimgl" },
|
||||
{ name = "pyqt6" },
|
||||
{ name = "pyside6" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "manim-slides", extra = ["full", "magic", "manim", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "myst-parser", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "pandoc" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-env" },
|
||||
{ name = "pytest-missing-modules" },
|
||||
{ name = "pytest-qt" },
|
||||
{ name = "setuptools" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "sphinx-click" },
|
||||
{ name = "sphinx-copybutton" },
|
||||
{ name = "sphinx-design" },
|
||||
{ name = "sphinxcontrib-programoutput" },
|
||||
{ name = "sphinxext-opengraph" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "bump-my-version" },
|
||||
{ name = "pre-commit" },
|
||||
docs = [
|
||||
{ name = "furo" },
|
||||
{ name = "ipykernel" },
|
||||
{ name = "manim-slides", extra = ["magic", "manim", "pyqt6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "myst-parser", version = "4.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "nbsphinx" },
|
||||
{ name = "pandoc" },
|
||||
{ name = "pygments" },
|
||||
{ name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "sphinx-click" },
|
||||
{ name = "sphinx-copybutton" },
|
||||
{ name = "sphinx-design" },
|
||||
{ name = "sphinxcontrib-programoutput" },
|
||||
{ name = "sphinxext-opengraph" },
|
||||
]
|
||||
tests = [
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'" },
|
||||
{ name = "manim-slides", extra = ["full", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-cov" },
|
||||
{ name = "pytest-env" },
|
||||
{ name = "pytest-missing-modules" },
|
||||
{ name = "pytest-qt" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
|
||||
@ -1640,72 +1652,89 @@ requires-dist = [
|
||||
{ name = "beautifulsoup4", specifier = ">=4.12.3" },
|
||||
{ name = "click", specifier = ">=8.1.3" },
|
||||
{ name = "click-default-group", specifier = ">=1.2.2" },
|
||||
{ name = "docutils", marker = "extra == 'docs'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'full'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'pyqt6-full'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'pyside6-full'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'sphinx-directive'", specifier = ">=0.20.1" },
|
||||
{ name = "docutils", marker = "extra == 'tests'", specifier = ">=0.20.1" },
|
||||
{ name = "furo", marker = "extra == 'docs'", specifier = ">=2023.5.20" },
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10' and extra == 'tests'", specifier = ">=8.6.1" },
|
||||
{ name = "ipykernel", marker = "extra == 'docs'", specifier = ">=6.25.1" },
|
||||
{ name = "ipython", marker = "extra == 'docs'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'full'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'magic'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'pyqt6-full'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'pyside6-full'", specifier = ">=8.12.2" },
|
||||
{ name = "ipython", marker = "extra == 'tests'", specifier = ">=8.12.2" },
|
||||
{ name = "jinja2", specifier = ">=3.1.2" },
|
||||
{ name = "lxml", specifier = ">=4.9.2" },
|
||||
{ name = "manim", marker = "extra == 'docs'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'full'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'magic'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'manim'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'pyqt6-full'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'pyside6-full'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'sphinx-directive'", specifier = ">=0.19" },
|
||||
{ name = "manim", marker = "extra == 'tests'", specifier = ">=0.19" },
|
||||
{ name = "manimgl", marker = "extra == 'manimgl'", specifier = ">=1.7.2" },
|
||||
{ name = "manimgl", marker = "extra == 'tests'", specifier = ">=1.7.2" },
|
||||
{ name = "myst-parser", marker = "extra == 'docs'", specifier = ">=2.0.0" },
|
||||
{ name = "nbsphinx", marker = "extra == 'docs'", specifier = ">=0.9.2" },
|
||||
{ name = "numpy", specifier = ">=1.19" },
|
||||
{ name = "pandoc", marker = "extra == 'docs'", specifier = ">=2.3" },
|
||||
{ name = "pillow", specifier = ">=9.5.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.1" },
|
||||
{ name = "pydantic-extra-types", specifier = ">=2.0.0" },
|
||||
{ name = "pygments", marker = "extra == 'docs'", specifier = "<2.19" },
|
||||
{ name = "pyqt6", marker = "extra == 'docs'", specifier = ">=6.7.0" },
|
||||
{ name = "pyqt6", marker = "extra == 'pyqt6'", specifier = ">=6.7.0" },
|
||||
{ name = "pyqt6", marker = "extra == 'pyqt6-full'", specifier = ">=6.7.0" },
|
||||
{ name = "pyqt6", marker = "extra == 'tests'", specifier = ">=6.7.0" },
|
||||
{ name = "pyside6", marker = "extra == 'pyside6'", specifier = ">=6.6.1,!=6.8.1.1" },
|
||||
{ name = "pyside6", marker = "extra == 'pyside6-full'", specifier = ">=6.6.1,!=6.8.1.1" },
|
||||
{ name = "pyside6", marker = "extra == 'tests'", specifier = ">=6.6.1,!=6.8.1.1" },
|
||||
{ name = "pytest", marker = "extra == 'tests'", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", marker = "extra == 'tests'", specifier = ">=4.1.0" },
|
||||
{ name = "pytest-env", marker = "extra == 'tests'", specifier = ">=0.8.2" },
|
||||
{ name = "pytest-missing-modules", marker = "extra == 'tests'", specifier = ">=0.1.0" },
|
||||
{ name = "pytest-qt", marker = "extra == 'tests'", specifier = ">=4.2.0" },
|
||||
{ name = "python-pptx", specifier = ">=0.6.21" },
|
||||
{ name = "qtpy", specifier = ">=2.4.1" },
|
||||
{ name = "requests", specifier = ">=2.28.1" },
|
||||
{ name = "rich", specifier = ">=13.3.2" },
|
||||
{ name = "rtoml", specifier = ">=0.11.0" },
|
||||
{ name = "sphinx", marker = "extra == 'docs'", specifier = ">=7.0.1" },
|
||||
{ name = "sphinx-click", marker = "extra == 'docs'", specifier = ">=4.4.0" },
|
||||
{ name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.1" },
|
||||
{ name = "sphinx-design", marker = "extra == 'docs'", specifier = ">=0.6.1" },
|
||||
{ name = "sphinxcontrib-programoutput", marker = "extra == 'docs'", specifier = ">=0.18" },
|
||||
{ name = "sphinxext-opengraph", marker = "extra == 'docs'", specifier = ">=0.7.5" },
|
||||
{ name = "tqdm", specifier = ">=4.64.1" },
|
||||
]
|
||||
provides-extras = ["docs", "full", "magic", "manim", "manimgl", "pyqt6", "pyqt6-full", "pyside6", "pyside6-full", "sphinx-directive", "tests"]
|
||||
provides-extras = ["full", "magic", "manim", "manimgl", "pyqt6", "pyqt6-full", "pyside6", "pyside6-full", "sphinx-directive"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "bump-my-version", specifier = ">=0.20.3" },
|
||||
{ name = "furo", specifier = ">=2023.5.20" },
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.6.1" },
|
||||
{ name = "ipykernel", specifier = ">=6.25.1" },
|
||||
{ name = "manim-slides", extras = ["full", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "manim-slides", extras = ["magic", "manim", "pyqt6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", specifier = ">=2.0.0" },
|
||||
{ name = "nbsphinx", specifier = ">=0.9.2" },
|
||||
{ name = "pandoc", specifier = ">=2.3" },
|
||||
{ name = "pre-commit", specifier = ">=3.5.0" },
|
||||
{ name = "pygments", specifier = "<2.19" },
|
||||
{ name = "pytest", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", specifier = ">=4.1.0" },
|
||||
{ name = "pytest-env", specifier = ">=0.8.2" },
|
||||
{ name = "pytest-missing-modules", specifier = ">=0.1.0" },
|
||||
{ name = "pytest-qt", specifier = ">=4.2.0" },
|
||||
{ name = "setuptools", specifier = ">=73.0.1" },
|
||||
{ name = "sphinx", specifier = ">=7.0.1" },
|
||||
{ name = "sphinx-click", specifier = ">=4.4.0" },
|
||||
{ name = "sphinx-copybutton", specifier = ">=0.5.1" },
|
||||
{ name = "sphinx-design", specifier = ">=0.6.1" },
|
||||
{ name = "sphinxcontrib-programoutput", specifier = ">=0.18" },
|
||||
{ name = "sphinxext-opengraph", specifier = ">=0.7.5" },
|
||||
]
|
||||
docs = [
|
||||
{ name = "furo", specifier = ">=2023.5.20" },
|
||||
{ name = "ipykernel", specifier = ">=6.25.1" },
|
||||
{ name = "manim-slides", extras = ["magic", "manim", "pyqt6", "sphinx-directive"] },
|
||||
{ name = "myst-parser", specifier = ">=2.0.0" },
|
||||
{ name = "nbsphinx", specifier = ">=0.9.2" },
|
||||
{ name = "pandoc", specifier = ">=2.3" },
|
||||
{ name = "pygments", specifier = "<2.19" },
|
||||
{ name = "sphinx", specifier = ">=7.0.1" },
|
||||
{ name = "sphinx-click", specifier = ">=4.4.0" },
|
||||
{ name = "sphinx-copybutton", specifier = ">=0.5.1" },
|
||||
{ name = "sphinx-design", specifier = ">=0.6.1" },
|
||||
{ name = "sphinxcontrib-programoutput", specifier = ">=0.18" },
|
||||
{ name = "sphinxext-opengraph", specifier = ">=0.7.5" },
|
||||
]
|
||||
tests = [
|
||||
{ name = "importlib-metadata", marker = "python_full_version < '3.10'", specifier = ">=8.6.1" },
|
||||
{ name = "manim-slides", extras = ["full", "manimgl", "pyqt6", "pyside6", "sphinx-directive"] },
|
||||
{ name = "pytest", specifier = ">=7.4.0" },
|
||||
{ name = "pytest-cov", specifier = ">=4.1.0" },
|
||||
{ name = "pytest-env", specifier = ">=0.8.2" },
|
||||
{ name = "pytest-missing-modules", specifier = ">=0.1.0" },
|
||||
{ name = "pytest-qt", specifier = ">=4.2.0" },
|
||||
{ name = "setuptools", specifier = ">=73.0.1" },
|
||||
]
|
||||
|
||||
|
Reference in New Issue
Block a user