mirror of
https://github.com/jeertmans/manim-slides.git
synced 2025-05-22 04:56:24 +08:00
chore(lib): add missing type hints and mypy is happy! (#75)
* chore(lib): add missing type hints and mypy is happy! Closes #34 * fix(ci): add missing dep for mypy
This commit is contained in:
@ -29,12 +29,12 @@ repos:
|
||||
rev: 'v0.991'
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-setuptools]
|
||||
args:
|
||||
- --install-types
|
||||
- --non-interactive
|
||||
- --ignore-missing-imports
|
||||
# Disallow dynamic typing
|
||||
- --disallow-any-unimported
|
||||
- --disallow-any-generics
|
||||
- --disallow-subclassing-any
|
||||
|
||||
@ -49,7 +49,7 @@ repos:
|
||||
# Configuring warnings
|
||||
- --warn-unused-ignores
|
||||
- --warn-no-return
|
||||
- --warn-return-any
|
||||
- --no-warn-return-any
|
||||
- --warn-redundant-casts
|
||||
|
||||
# Strict equality
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Callable
|
||||
from typing import Any, Callable
|
||||
|
||||
import click
|
||||
from click import Context, Parameter
|
||||
@ -6,10 +6,13 @@ from click import Context, Parameter
|
||||
from .defaults import CONFIG_PATH, FOLDER_PATH
|
||||
from .manim import logger
|
||||
|
||||
F = Callable[..., Any]
|
||||
Wrapper = Callable[[F], F]
|
||||
|
||||
def config_path_option(function: Callable) -> Callable:
|
||||
|
||||
def config_path_option(function: F) -> F:
|
||||
"""Wraps a function to add configuration path option."""
|
||||
return click.option(
|
||||
wrapper: Wrapper = click.option(
|
||||
"-c",
|
||||
"--config",
|
||||
"config_path",
|
||||
@ -18,10 +21,11 @@ def config_path_option(function: Callable) -> Callable:
|
||||
type=click.Path(dir_okay=False),
|
||||
help="Set path to configuration file.",
|
||||
show_default=True,
|
||||
)(function)
|
||||
)
|
||||
return wrapper(function)
|
||||
|
||||
|
||||
def config_options(function: Callable) -> Callable:
|
||||
def config_options(function: F) -> F:
|
||||
"""Wraps a function to add configuration options."""
|
||||
function = config_path_option(function)
|
||||
function = click.option(
|
||||
@ -36,7 +40,7 @@ def config_options(function: Callable) -> Callable:
|
||||
return function
|
||||
|
||||
|
||||
def verbosity_option(function: Callable) -> Callable:
|
||||
def verbosity_option(function: F) -> F:
|
||||
"""Wraps a function to add verbosity option."""
|
||||
|
||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||
@ -46,7 +50,7 @@ def verbosity_option(function: Callable) -> Callable:
|
||||
|
||||
logger.setLevel(value)
|
||||
|
||||
return click.option(
|
||||
wrapper: Wrapper = click.option(
|
||||
"-v",
|
||||
"--verbosity",
|
||||
type=click.Choice(
|
||||
@ -59,16 +63,20 @@ def verbosity_option(function: Callable) -> Callable:
|
||||
envvar="MANIM_SLIDES_VERBOSITY",
|
||||
show_envvar=True,
|
||||
callback=callback,
|
||||
)(function)
|
||||
)
|
||||
|
||||
return wrapper(function)
|
||||
|
||||
|
||||
def folder_path_option(function: Callable) -> Callable:
|
||||
def folder_path_option(function: F) -> F:
|
||||
"""Wraps a function to add folder path option."""
|
||||
return click.option(
|
||||
wrapper: Wrapper = click.option(
|
||||
"--folder",
|
||||
metavar="DIRECTORY",
|
||||
default=FOLDER_PATH,
|
||||
type=click.Path(exists=True, file_okay=False),
|
||||
help="Set slides folder.",
|
||||
show_default=True,
|
||||
)(function)
|
||||
)
|
||||
|
||||
return wrapper(function)
|
||||
|
@ -3,7 +3,7 @@ import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Set
|
||||
from typing import Callable, Dict, List, Optional, Set, Union
|
||||
|
||||
from pydantic import BaseModel, root_validator, validator
|
||||
from PySide6.QtCore import Qt
|
||||
@ -24,7 +24,7 @@ def merge_basenames(files: List[str]) -> str:
|
||||
return os.path.join(dirname, basename + ext)
|
||||
|
||||
|
||||
class Key(BaseModel):
|
||||
class Key(BaseModel): # type: ignore
|
||||
"""Represents a list of key codes, with optionally a name."""
|
||||
|
||||
ids: Set[int]
|
||||
@ -48,7 +48,7 @@ class Key(BaseModel):
|
||||
return m
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
class Config(BaseModel): # type: ignore
|
||||
"""General Manim Slides config"""
|
||||
|
||||
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
|
||||
@ -60,8 +60,8 @@ class Config(BaseModel):
|
||||
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
|
||||
|
||||
@root_validator
|
||||
def ids_are_unique_across_keys(cls, values):
|
||||
ids = set()
|
||||
def ids_are_unique_across_keys(cls, values: Dict[str, Key]) -> Dict[str, Key]:
|
||||
ids: Set[int] = set()
|
||||
|
||||
for key in values.values():
|
||||
if len(ids.intersection(key.ids)) != 0:
|
||||
@ -87,7 +87,7 @@ class SlideType(str, Enum):
|
||||
last = "last"
|
||||
|
||||
|
||||
class SlideConfig(BaseModel):
|
||||
class SlideConfig(BaseModel): # type: ignore
|
||||
type: SlideType
|
||||
start_animation: int
|
||||
end_animation: int
|
||||
@ -95,20 +95,22 @@ class SlideConfig(BaseModel):
|
||||
terminated: bool = False
|
||||
|
||||
@validator("start_animation", "end_animation")
|
||||
def index_is_posint(cls, v: int):
|
||||
def index_is_posint(cls, v: int) -> int:
|
||||
if v < 0:
|
||||
raise ValueError("Animation index (start or end) cannot be negative")
|
||||
return v
|
||||
|
||||
@validator("number")
|
||||
def number_is_strictly_posint(cls, v: int):
|
||||
def number_is_strictly_posint(cls, v: int) -> int:
|
||||
if v <= 0:
|
||||
raise ValueError("Slide number cannot be negative or zero")
|
||||
return v
|
||||
|
||||
@root_validator
|
||||
def start_animation_is_before_end(cls, values):
|
||||
if values["start_animation"] >= values["end_animation"]:
|
||||
def start_animation_is_before_end(
|
||||
cls, values: Dict[str, Union[SlideType, int, bool]]
|
||||
) -> Dict[str, Union[SlideType, int, bool]]:
|
||||
if values["start_animation"] >= values["end_animation"]: # type: ignore
|
||||
|
||||
if values["start_animation"] == values["end_animation"] == 0:
|
||||
raise ValueError(
|
||||
@ -135,12 +137,12 @@ class SlideConfig(BaseModel):
|
||||
return slice(self.start_animation, self.end_animation)
|
||||
|
||||
|
||||
class PresentationConfig(BaseModel):
|
||||
class PresentationConfig(BaseModel): # type: ignore
|
||||
slides: List[SlideConfig]
|
||||
files: List[str]
|
||||
|
||||
@validator("files", pre=True, each_item=True)
|
||||
def is_file_and_exists(cls, v):
|
||||
def is_file_and_exists(cls, v: str) -> str:
|
||||
if not os.path.exists(v):
|
||||
raise ValueError(
|
||||
f"Animation file {v} does not exist. Are you in the right directory?"
|
||||
@ -152,7 +154,9 @@ class PresentationConfig(BaseModel):
|
||||
return v
|
||||
|
||||
@root_validator
|
||||
def animation_indices_match_files(cls, values):
|
||||
def animation_indices_match_files(
|
||||
cls, values: Dict[str, Union[List[SlideConfig], List[str]]]
|
||||
) -> Dict[str, Union[List[SlideConfig], List[str]]]:
|
||||
files = values.get("files")
|
||||
slides = values.get("slides")
|
||||
|
||||
@ -162,18 +166,20 @@ class PresentationConfig(BaseModel):
|
||||
n_files = len(files)
|
||||
|
||||
for slide in slides:
|
||||
if slide.end_animation > n_files:
|
||||
if slide.end_animation > n_files: # type: ignore
|
||||
raise ValueError(
|
||||
f"The following slide's contains animations not listed in files {files}: {slide}"
|
||||
)
|
||||
|
||||
return values
|
||||
|
||||
def move_to(self, dest: str, copy=True) -> "PresentationConfig":
|
||||
def move_to(self, dest: str, copy: bool = True) -> "PresentationConfig":
|
||||
"""
|
||||
Moves (or copy) the files to a given directory.
|
||||
"""
|
||||
move = shutil.copy if copy else shutil.move
|
||||
copy_func: Callable[[str, str], None] = shutil.copy
|
||||
move_func: Callable[[str, str], None] = shutil.move
|
||||
move = copy_func if copy else move_func
|
||||
|
||||
n = len(self.files)
|
||||
for i in range(n):
|
||||
|
@ -31,11 +31,11 @@ def validate_config_option(
|
||||
return config
|
||||
|
||||
|
||||
class Converter(BaseModel):
|
||||
class Converter(BaseModel): # type: ignore
|
||||
presentation_configs: List[PresentationConfig] = []
|
||||
assets_dir: str = "{basename}_assets"
|
||||
|
||||
def convert_to(self, dest: str):
|
||||
def convert_to(self, dest: str) -> None:
|
||||
"""Converts self, i.e., a list of presentations, into a given format."""
|
||||
raise NotImplementedError
|
||||
|
||||
@ -105,7 +105,7 @@ class RevealJS(Converter):
|
||||
__name__, "data/revealjs_template.html"
|
||||
).decode()
|
||||
|
||||
def convert_to(self, dest: str):
|
||||
def convert_to(self, dest: str) -> None:
|
||||
"""Converts this configuration into a RevealJS HTML presentation, saved to DEST."""
|
||||
dirname = os.path.dirname(dest)
|
||||
basename, ext = os.path.splitext(os.path.basename(dest))
|
||||
@ -130,7 +130,7 @@ class RevealJS(Converter):
|
||||
f.write(content)
|
||||
|
||||
|
||||
def show_config_options(function: Callable) -> Callable:
|
||||
def show_config_options(function: Callable[..., Any]) -> Callable[..., Any]:
|
||||
"""Wraps a function to add a `--show-config` option."""
|
||||
|
||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||
@ -191,7 +191,15 @@ def show_config_options(function: Callable) -> Callable:
|
||||
)
|
||||
@show_config_options
|
||||
@verbosity_option
|
||||
def convert(scenes, folder, dest, to, open_result, force, config_options):
|
||||
def convert(
|
||||
scenes: List[str],
|
||||
folder: str,
|
||||
dest: str,
|
||||
to: str,
|
||||
open_result: bool,
|
||||
force: bool,
|
||||
config_options: Dict[str, str],
|
||||
) -> None:
|
||||
"""
|
||||
Convert SCENE(s) into a given format and writes the result in DEST.
|
||||
"""
|
||||
|
@ -3,7 +3,7 @@ import platform
|
||||
import sys
|
||||
import time
|
||||
from enum import IntEnum, auto, unique
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import click
|
||||
import cv2
|
||||
@ -11,7 +11,7 @@ import numpy as np
|
||||
from pydantic import ValidationError
|
||||
from PySide6 import QtGui
|
||||
from PySide6.QtCore import Qt, QThread, Signal, Slot
|
||||
from PySide6.QtGui import QIcon, QPixmap
|
||||
from PySide6.QtGui import QCloseEvent, QIcon, QKeyEvent, QPixmap, QResizeEvent
|
||||
from PySide6.QtWidgets import QApplication, QGridLayout, QLabel, QWidget
|
||||
from tqdm import tqdm
|
||||
|
||||
@ -67,7 +67,7 @@ class Presentation:
|
||||
|
||||
self.current_slide_index: int = 0
|
||||
self.current_animation: int = self.current_slide.start_animation
|
||||
self.current_file: Optional[str] = None
|
||||
self.current_file: str = ""
|
||||
|
||||
self.loaded_animation_cap: int = -1
|
||||
self.cap = None # cap = cv2.VideoCapture
|
||||
@ -222,7 +222,7 @@ class Presentation:
|
||||
"""Returns current frame number."""
|
||||
return int(self.current_cap.get(cv2.CAP_PROP_POS_FRAMES))
|
||||
|
||||
def update_state(self, state) -> Tuple[np.ndarray, State]:
|
||||
def update_state(self, state: State) -> Tuple[np.ndarray, State]:
|
||||
"""
|
||||
Updates the current state given the previous one.
|
||||
|
||||
@ -236,7 +236,7 @@ class Presentation:
|
||||
still_playing, frame = self.current_cap.read()
|
||||
if still_playing:
|
||||
self.lastframe = frame
|
||||
elif state in [state.WAIT, state.PAUSED]:
|
||||
elif state == state.WAIT or state == state.PAUSED: # type: ignore
|
||||
return self.lastframe, state
|
||||
elif self.current_slide.is_last() and self.current_slide.terminated:
|
||||
return self.lastframe, State.END
|
||||
@ -268,7 +268,7 @@ class Presentation:
|
||||
return self.lastframe, state
|
||||
|
||||
|
||||
class Display(QThread):
|
||||
class Display(QThread): # type: ignore
|
||||
"""Displays one or more presentations one after each other."""
|
||||
|
||||
change_video_signal = Signal(np.ndarray)
|
||||
@ -277,12 +277,12 @@ class Display(QThread):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
presentations,
|
||||
presentations: List[PresentationConfig],
|
||||
config: Config = DEFAULT_CONFIG,
|
||||
start_paused=False,
|
||||
skip_all=False,
|
||||
record_to=None,
|
||||
exit_after_last_slide=False,
|
||||
start_paused: bool = False,
|
||||
skip_all: bool = False,
|
||||
record_to: Optional[str] = None,
|
||||
exit_after_last_slide: bool = False,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.presentations = presentations
|
||||
@ -444,14 +444,14 @@ class Display(QThread):
|
||||
|
||||
self.key = -1 # No more key to be handled
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
"""Stops current thread, without doing anything after."""
|
||||
self.run_flag = False
|
||||
self.wait()
|
||||
|
||||
|
||||
class Info(QWidget):
|
||||
def __init__(self):
|
||||
class Info(QWidget): # type: ignore
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.setWindowTitle(WINDOW_INFO_NAME)
|
||||
|
||||
@ -474,7 +474,7 @@ class Info(QWidget):
|
||||
self.update_info({})
|
||||
|
||||
@Slot(dict)
|
||||
def update_info(self, info: dict):
|
||||
def update_info(self, info: Dict[str, Union[str, int]]) -> None:
|
||||
self.animationLabel.setText("Animation: {}".format(info.get("animation", "na")))
|
||||
self.stateLabel.setText("State: {}".format(info.get("state", "unknown")))
|
||||
self.slideLabel.setText(
|
||||
@ -490,27 +490,27 @@ class Info(QWidget):
|
||||
)
|
||||
|
||||
|
||||
class InfoThread(QThread):
|
||||
def __init__(self):
|
||||
class InfoThread(QThread): # type: ignore
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.dialog = Info()
|
||||
self.run_flag = True
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
super().start()
|
||||
|
||||
self.dialog.show()
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
self.dialog.deleteLater()
|
||||
|
||||
|
||||
class App(QWidget):
|
||||
class App(QWidget): # type: ignore
|
||||
send_key_signal = Signal(int)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
*args: Any,
|
||||
config: Config = DEFAULT_CONFIG,
|
||||
fullscreen: bool = False,
|
||||
resolution: Tuple[int, int] = (1980, 1080),
|
||||
@ -518,7 +518,7 @@ class App(QWidget):
|
||||
aspect_ratio: Qt.AspectRatioMode = Qt.IgnoreAspectRatio,
|
||||
resize_mode: Qt.TransformationMode = Qt.SmoothTransformation,
|
||||
background_color: str = "black",
|
||||
**kwargs,
|
||||
**kwargs: Any,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
@ -543,7 +543,8 @@ class App(QWidget):
|
||||
self.label.setMinimumSize(1, 1)
|
||||
|
||||
# create the video capture thread
|
||||
self.thread = Display(*args, config=config, **kwargs)
|
||||
kwargs["config"] = config
|
||||
self.thread = Display(*args, **kwargs)
|
||||
# create the info dialog
|
||||
self.info = Info()
|
||||
self.info.show()
|
||||
@ -563,7 +564,7 @@ class App(QWidget):
|
||||
# start the thread
|
||||
self.thread.start()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
|
||||
key = event.key()
|
||||
if self.config.HIDE_MOUSE.match(key):
|
||||
@ -577,35 +578,30 @@ class App(QWidget):
|
||||
self.send_key_signal.emit(key)
|
||||
event.accept()
|
||||
|
||||
def closeAll(self):
|
||||
def closeAll(self) -> None:
|
||||
logger.debug("Closing all QT windows")
|
||||
self.thread.stop()
|
||||
self.info.deleteLater()
|
||||
self.deleteLater()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
def resizeEvent(self, event: QResizeEvent) -> None:
|
||||
self.pixmap = self.pixmap.scaled(
|
||||
self.width(), self.height(), self.aspect_ratio, self.resize_mode
|
||||
)
|
||||
self.label.setPixmap(self.pixmap)
|
||||
self.label.resize(self.width(), self.height())
|
||||
|
||||
def closeEvent(self, event):
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
self.closeAll()
|
||||
event.accept()
|
||||
|
||||
@Slot(np.ndarray)
|
||||
def update_image(self, cv_img: dict):
|
||||
"""Updates the image_label with a new opencv image"""
|
||||
def update_image(self, cv_img: np.ndarray) -> None:
|
||||
"""Updates the (image) label with a new opencv image"""
|
||||
self.pixmap = self.convert_cv_qt(cv_img)
|
||||
self.label.setPixmap(self.pixmap)
|
||||
|
||||
@Slot(dict)
|
||||
def update_info(self, info: dict):
|
||||
"""Updates the image_label with a new opencv image"""
|
||||
pass
|
||||
|
||||
def convert_cv_qt(self, cv_img):
|
||||
def convert_cv_qt(self, cv_img: np.ndarray) -> np.ndarray:
|
||||
"""Convert from an opencv image to QPixmap"""
|
||||
rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
|
||||
h, w, ch = rgb_image.shape
|
||||
@ -633,14 +629,14 @@ class App(QWidget):
|
||||
)
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def list_scenes(folder) -> None:
|
||||
def list_scenes(folder: str) -> None:
|
||||
"""List available scenes."""
|
||||
|
||||
for i, scene in enumerate(_list_scenes(folder), start=1):
|
||||
click.secho(f"{i}: {scene}", fg="green")
|
||||
|
||||
|
||||
def _list_scenes(folder) -> List[str]:
|
||||
def _list_scenes(folder: str) -> List[str]:
|
||||
"""Lists available scenes in given directory."""
|
||||
scenes = []
|
||||
|
||||
@ -792,19 +788,19 @@ def get_scenes_presentation_config(
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def present(
|
||||
scenes,
|
||||
config_path,
|
||||
folder,
|
||||
start_paused,
|
||||
fullscreen,
|
||||
skip_all,
|
||||
resolution,
|
||||
record_to,
|
||||
exit_after_last_slide,
|
||||
hide_mouse,
|
||||
aspect_ratio,
|
||||
resize_mode,
|
||||
background_color,
|
||||
scenes: List[str],
|
||||
config_path: str,
|
||||
folder: str,
|
||||
start_paused: bool,
|
||||
fullscreen: bool,
|
||||
skip_all: bool,
|
||||
resolution: Tuple[int, int],
|
||||
record_to: Optional[str],
|
||||
exit_after_last_slide: bool,
|
||||
hide_mouse: bool,
|
||||
aspect_ratio: str,
|
||||
resize_mode: str,
|
||||
background_color: str,
|
||||
) -> None:
|
||||
"""
|
||||
Present SCENE(s), one at a time, in order.
|
||||
|
@ -25,7 +25,7 @@ def reverse_video_file(src: str, dst: str) -> None:
|
||||
logger.debug(error.decode())
|
||||
|
||||
|
||||
class Slide(Scene):
|
||||
class Slide(Scene): # type:ignore
|
||||
"""
|
||||
Inherits from `manim.Scene` or `manimlib.Scene` and provide necessary tools for slides rendering.
|
||||
"""
|
||||
@ -222,7 +222,7 @@ class Slide(Scene):
|
||||
self.save_slides()
|
||||
|
||||
|
||||
class ThreeDSlide(Slide, ThreeDScene):
|
||||
class ThreeDSlide(Slide, ThreeDScene): # type: ignore
|
||||
"""
|
||||
Inherits from `manim.ThreeDScene` or `manimlib.ThreeDScene` and provide necessary tools for slides rendering.
|
||||
|
||||
|
@ -5,7 +5,7 @@ from typing import Any
|
||||
|
||||
import click
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtGui import QIcon, QKeyEvent
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QDialog,
|
||||
@ -31,7 +31,7 @@ for key in Qt.Key:
|
||||
keymap[key.value] = key.name.partition("_")[2]
|
||||
|
||||
|
||||
class KeyInput(QDialog):
|
||||
class KeyInput(QDialog): # type: ignore
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.key = None
|
||||
@ -43,13 +43,13 @@ class KeyInput(QDialog):
|
||||
self.layout.addWidget(self.label)
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def keyPressEvent(self, event: Any) -> None:
|
||||
def keyPressEvent(self, event: QKeyEvent) -> None:
|
||||
self.key = event.key()
|
||||
self.deleteLater()
|
||||
event.accept()
|
||||
|
||||
|
||||
class Wizard(QWidget):
|
||||
class Wizard(QWidget): # type: ignore
|
||||
def __init__(self, config: Config):
|
||||
|
||||
super().__init__()
|
||||
@ -131,7 +131,7 @@ class Wizard(QWidget):
|
||||
@config_options
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def wizard(config_path, force, merge):
|
||||
def wizard(config_path: str, force: bool, merge: bool) -> None:
|
||||
"""Launch configuration wizard."""
|
||||
return _init(config_path, force, merge, skip_interactive=False)
|
||||
|
||||
@ -140,12 +140,16 @@ def wizard(config_path, force, merge):
|
||||
@config_options
|
||||
@click.help_option("-h", "--help")
|
||||
@verbosity_option
|
||||
def init(config_path, force, merge, skip_interactive=False):
|
||||
def init(
|
||||
config_path: str, force: bool, merge: bool, skip_interactive: bool = False
|
||||
) -> None:
|
||||
"""Initialize a new default configuration file."""
|
||||
return _init(config_path, force, merge, skip_interactive=True)
|
||||
|
||||
|
||||
def _init(config_path, force, merge, skip_interactive=False):
|
||||
def _init(
|
||||
config_path: str, force: bool, merge: bool, skip_interactive: bool = False
|
||||
) -> None:
|
||||
"""Actual initialization code for configuration file, with optional interactive mode."""
|
||||
|
||||
if os.path.exists(config_path):
|
||||
|
Reference in New Issue
Block a user