feat(cli): add basic logging facilities (#50)

* feat(cli): add basic logging facilities

This PR adds basic logging support, to be extended in the future.

* fix: typo in decorator name
This commit is contained in:
Jérome Eertmans
2022-10-17 10:52:42 +02:00
committed by GitHub
parent 5b9cb1523c
commit bc3d55fce2
4 changed files with 55 additions and 9 deletions

View File

@ -1,11 +1,13 @@
from typing import Callable
import click
from click import Context, Parameter
from .defaults import CONFIG_PATH
from .manim import logger
def config_path_option(function) -> Callable:
def config_path_option(function: Callable) -> Callable:
"""Wraps a function to add configuration path option."""
return click.option(
"-c",
@ -18,7 +20,7 @@ def config_path_option(function) -> Callable:
)(function)
def config_options(function) -> Callable:
def config_options(function: Callable) -> Callable:
"""Wraps a function to add configuration options."""
function = config_path_option(function)
function = click.option(
@ -31,3 +33,27 @@ def config_options(function) -> Callable:
help="Merge any existing configuration file with the new configuration.",
)(function)
return function
def verbosity_option(function: Callable) -> Callable:
"""Wraps a function to add verbosity option."""
def callback(ctx: Context, param: Parameter, value: bool) -> None:
if not value or ctx.resilient_parsing:
return
logger.setLevel(value)
return 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,
callback=callback,
)(function)

View File

@ -5,6 +5,7 @@ from typing import List, Optional, Set
from pydantic import BaseModel, root_validator, validator
from .defaults import LEFT_ARROW_KEY_CODE, RIGHT_ARROW_KEY_CODE
from .manim import logger
class Key(BaseModel):
@ -20,7 +21,12 @@ class Key(BaseModel):
return v
def match(self, key_id: int):
return key_id in self.ids
m = key_id in self.ids
if m:
logger.debug(f"Pressed key: {self.name}")
return m
class Config(BaseModel):

View File

@ -11,9 +11,10 @@ import numpy as np
from pydantic import ValidationError
from tqdm import tqdm
from .commons import config_path_option
from .commons import config_path_option, verbosity_option
from .config import Config, PresentationConfig, SlideConfig, SlideType
from .defaults import FOLDER_PATH, FONT_ARGS
from .manim import logger
INTERPOLATION_FLAGS = {
"nearest": cv2.INTER_NEAREST,
@ -450,6 +451,9 @@ class Display:
cv2.destroyAllWindows()
if self.record_to is not None and len(self.recordings) > 0:
logger.debug(
f"A total of {len(self.recordings)} frames will be saved to {self.record_to}"
)
file, frame_number, fps = self.recordings[0]
cap = cv2.VideoCapture(file)
@ -489,6 +493,7 @@ class Display:
show_default=True,
)
@click.help_option("-h", "--help")
@verbosity_option
def list_scenes(folder) -> None:
"""List available scenes."""
@ -502,13 +507,18 @@ def _list_scenes(folder) -> List[str]:
for file in os.listdir(folder):
if file.endswith(".json"):
filepath = os.path.join(folder, file)
try:
filepath = os.path.join(folder, file)
_ = PresentationConfig.parse_file(filepath)
scenes.append(os.path.basename(file)[:-5])
except Exception: # Could not parse this file as a proper presentation config
except Exception as e: # Could not parse this file as a proper presentation config
logger.warn(
f"Something went wrong with parsing presentation config `{filepath}`: {e}"
)
pass
logger.info(f"Found {len(scenes)} valid scene configuration files in `{folder}`.")
return scenes
@ -552,6 +562,7 @@ def _list_scenes(folder) -> List[str]:
help="If set, the presentation will be recorded into a AVI video file with given name.",
)
@click.help_option("-h", "--help")
@verbosity_option
def present(
scenes,
config_path,
@ -616,8 +627,8 @@ def present(
f"File {config_file} does not exist, check the scene name and make sure to use Slide as your scene base class"
)
try:
config = PresentationConfig.parse_file(config_file)
presentations.append(Presentation(config))
pres_config = PresentationConfig.parse_file(config_file)
presentations.append(Presentation(pres_config))
except ValidationError as e:
raise click.UsageError(str(e))
@ -627,6 +638,7 @@ def present(
except ValidationError as e:
raise click.UsageError(str(e))
else:
logger.info("No configuration file found, using default configuration.")
config = Config()
if record_to is not None:

View File

@ -5,7 +5,7 @@ import click
import cv2
import numpy as np
from .commons import config_options
from .commons import config_options, verbosity_option
from .config import Config
from .defaults import CONFIG_PATH, FONT_ARGS
@ -39,6 +39,7 @@ def prompt(question: str) -> int:
@click.command()
@config_options
@click.help_option("-h", "--help")
@verbosity_option
def wizard(config_path, force, merge):
"""Launch configuration wizard."""
return _init(config_path, force, merge, skip_interactive=False)
@ -47,6 +48,7 @@ def wizard(config_path, force, merge):
@click.command()
@config_options
@click.help_option("-h", "--help")
@verbosity_option
def init(config_path, force, merge, skip_interactive=False):
"""Initialize a new default configuration file."""
return _init(config_path, force, merge, skip_interactive=True)