feat(lib): change config file from json to toml (#224)

This PR introduces a **BREAKING CHANGE**: the general config file is now written in the TOML format. Keys are also placed under a shared subsection. Previous config files will be ignore.

Migration from the previous format can be easily performed with `manim-slides init` and copy/pasting the key codes if necessary.
This commit is contained in:
Jérome Eertmans
2023-07-24 14:46:15 +02:00
committed by GitHub
parent 529a6c534f
commit a7719dbb8b
7 changed files with 117 additions and 50 deletions

View File

@ -8,6 +8,7 @@ from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple, Union
import rtoml
from pydantic import (
BaseModel,
Field,
@ -46,7 +47,7 @@ def merge_basenames(files: List[FilePath]) -> Path:
class Key(BaseModel): # type: ignore
"""Represents a list of key codes, with optionally a name."""
ids: Set[PositiveInt]
ids: List[PositiveInt] = Field(unique=True)
name: Optional[str] = None
@field_validator("ids")
@ -57,7 +58,7 @@ class Key(BaseModel): # type: ignore
return ids
def set_ids(self, *ids: int) -> None:
self.ids = set(ids)
self.ids = list(set(ids))
def match(self, key_id: int) -> bool:
m = key_id in self.ids
@ -68,9 +69,7 @@ class Key(BaseModel): # type: ignore
return m
class Config(BaseModel): # type: ignore
"""General Manim Slides config"""
class Keys(BaseModel): # type: ignore
QUIT: Key = Key(ids=[Qt.Key_Q], name="QUIT")
CONTINUE: Key = Key(ids=[Qt.Key_Right], name="CONTINUE / NEXT")
BACK: Key = Key(ids=[Qt.Key_Left], name="BACK")
@ -79,17 +78,6 @@ class Config(BaseModel): # type: ignore
PLAY_PAUSE: Key = Key(ids=[Qt.Key_Space], name="PLAY / PAUSE")
HIDE_MOUSE: Key = Key(ids=[Qt.Key_H], name="HIDE / SHOW MOUSE")
@classmethod
def from_file(cls, path: Path) -> "Config":
"""Reads a configuration from a file."""
with open(path, "r") as f:
return cls.model_validate_json(f.read()) # type: ignore
def to_file(self, path: Path) -> None:
"""Dumps the configuration to a file."""
with open(path, "w") as f:
f.write(self.model_dump_json(indent=2))
@model_validator(mode="before")
def ids_are_unique_across_keys(cls, values: Dict[str, Key]) -> Dict[str, Key]:
ids: Set[int] = set()
@ -103,15 +91,35 @@ class Config(BaseModel): # type: ignore
return values
def merge_with(self, other: "Config") -> "Config":
def merge_with(self, other: "Keys") -> "Keys":
for key_name, key in self:
other_key = getattr(other, key_name)
key.ids.update(other_key.ids)
print(set(key.ids))
key.ids = list(set(key.ids).union(other_key.ids))
key.name = other_key.name or key.name
return self
class Config(BaseModel): # type: ignore
"""General Manim Slides config"""
keys: Keys = Keys()
@classmethod
def from_file(cls, path: Path) -> "Config":
"""Reads a configuration from a file."""
return cls.model_validate(rtoml.load(path)) # type: ignore
def to_file(self, path: Path) -> None:
"""Dumps the configuration to a file."""
rtoml.dump(self.model_dump(), path, pretty=True)
def merge_with(self, other: "Config") -> "Config":
self.keys = self.keys.merge_with(other.keys)
return self
class SlideType(str, Enum):
slide = "slide"
loop = "loop"

View File

@ -1,3 +1,3 @@
FOLDER_PATH: str = "./slides"
CONFIG_PATH: str = ".manim-slides.json"
CONFIG_PATH: str = ".manim-slides.toml"
FFMPEG_BIN: str = "ffmpeg"

View File

@ -539,23 +539,24 @@ class Display(QThread): # type: ignore
"""Handles key strokes."""
key = self.key
keys = self.config.keys
if self.config.QUIT.match(key):
if keys.QUIT.match(key):
self.run_flag = False
elif self.state == State.PLAYING and self.config.PLAY_PAUSE.match(key):
elif self.state == State.PLAYING and keys.PLAY_PAUSE.match(key):
self.state = State.PAUSED
elif self.state == State.PAUSED and self.config.PLAY_PAUSE.match(key):
elif self.state == State.PAUSED and keys.PLAY_PAUSE.match(key):
self.state = State.PLAYING
elif self.state == State.WAIT and (
self.config.CONTINUE.match(key) or self.config.PLAY_PAUSE.match(key)
keys.CONTINUE.match(key) or keys.PLAY_PAUSE.match(key)
):
self.current_presentation.load_next_slide()
self.state = State.PLAYING
elif (
self.state == State.PLAYING and self.config.CONTINUE.match(key)
self.state == State.PLAYING and keys.CONTINUE.match(key)
) or self.skip_all:
self.current_presentation.load_next_slide()
elif self.config.BACK.match(key):
elif keys.BACK.match(key):
if self.current_presentation.current_slide_index == 0:
if self.current_presentation_index == 0:
self.current_presentation.load_previous_slide()
@ -566,10 +567,10 @@ class Display(QThread): # type: ignore
else:
self.current_presentation.load_previous_slide()
self.state = State.PLAYING
elif self.config.REVERSE.match(key):
elif keys.REVERSE.match(key):
self.current_presentation.reverse_current_slide()
self.state = State.PLAYING
elif self.config.REWIND.match(key):
elif keys.REWIND.match(key):
self.current_presentation.cancel_reverse()
self.current_presentation.rewind_current_slide()
self.state = State.PLAYING
@ -705,7 +706,7 @@ class App(QWidget): # type: ignore
def keyPressEvent(self, event: QKeyEvent) -> None:
key = event.key()
if self.config.HIDE_MOUSE.match(key):
if self.config.keys.HIDE_MOUSE.match(key):
if self.hide_mouse:
self.setCursor(Qt.ArrowCursor)
self.hide_mouse = False