Skip to content

Commit

Permalink
chore(lib): re-organizing the module
Browse files Browse the repository at this point in the history
This PR refactors a lot of the code in order to have a better organized codebase. If will that I needed to create a second level of submodules, to better distinguish the different parts of this project.
  • Loading branch information
jeertmans committed Jul 23, 2024
1 parent 8c0dd6a commit 3ab1a1c
Show file tree
Hide file tree
Showing 33 changed files with 474 additions and 300 deletions.
2 changes: 1 addition & 1 deletion docs/source/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,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.

Expand Down
2 changes: 1 addition & 1 deletion docs/source/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
2 changes: 1 addition & 1 deletion docs/source/reference/customize_html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
2 changes: 1 addition & 1 deletion docs/source/reference/html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 8 additions & 3 deletions manim_slides/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,9 +15,7 @@
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__(
Expand Down
73 changes: 3 additions & 70 deletions manim_slides/__main__.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,6 @@
import json
"""Manim Slides' main entrypoint."""

import click
import requests
from click_default_group import DefaultGroup

from .__version__ import __version__
from .convert import convert
from .logger import logger
from .present import list_scenes, present
from .render import render
from .wizard 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 cli(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}")


cli.add_command(convert)
cli.add_command(init)
cli.add_command(list_scenes)
cli.add_command(present)
cli.add_command(render)
cli.add_command(wizard)
from .cli.commands import main

if __name__ == "__main__":
cli()
main()
2 changes: 2 additions & 0 deletions manim_slides/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
"""Manim Slides' version."""

__version__ = "5.1.7"
72 changes: 72 additions & 0 deletions manim_slides/cli/commands.py
Original file line number Diff line number Diff line change
@@ -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)
145 changes: 145 additions & 0 deletions manim_slides/cli/commons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from pathlib import Path
from typing import Any, Callable

import click
from click import Context, Parameter

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]


def config_path_option(function: F) -> F:
"""Wrap a function to add configuration path option."""
wrapper: Wrapper = click.option(
"-c",
"--config",
"config_path",
metavar="FILE",
default=CONFIG_PATH,
type=click.Path(dir_okay=False, path_type=Path),
help="Set path to configuration file.",
show_default=True,
)
return wrapper(function)


def config_options(function: F) -> F:
"""Wrap a function to add configuration options."""
function = config_path_option(function)
function = click.option(
"-f", "--force", is_flag=True, help="Overwrite any existing configuration file."
)(function)
function = click.option(
"-m",
"--merge",
is_flag=True,
help="Merge any existing configuration file with the new configuration.",
)(function)
return function


def verbosity_option(function: F) -> F:
"""Wrap a function to add verbosity option."""

def callback(ctx: Context, param: Parameter, value: str) -> None:
if not value or ctx.resilient_parsing:
return

logger.setLevel(value)

wrapper: Wrapper = click.option(
"-v",
"--verbosity",
type=click.Choice(
["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
case_sensitive=False,
),
help="Verbosity of CLI output.",
default=None,
expose_value=False,
envvar="MANIM_SLIDES_VERBOSITY",
show_envvar=True,
callback=callback,
)

return wrapper(function)


def folder_path_option(function: F) -> F:
"""Wrap a function to add folder path option."""
wrapper: Wrapper = click.option(
"--folder",
metavar="DIRECTORY",
default=FOLDER_PATH,
type=click.Path(exists=True, file_okay=False, path_type=Path),
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)
File renamed without changes.
Loading

0 comments on commit 3ab1a1c

Please sign in to comment.