jean-web/jwebsite/content.py

110 lines
3.1 KiB
Python

from functools import cache
from gettext import textdomain
from pathlib import Path
from typing import Any, Iterable, Iterator
from markdown import Markdown
from yaml import Loader, load
class Content:
def __init__(self, path: Path) -> None:
self.__path = path
@property
def path(self) -> Path:
return self.__path
class ContentDirectory(Content):
def load(self, subpath: str | Path) -> Content:
subpath = Path(subpath)
current: Content = self
for part in subpath.parts:
if not isinstance(current, ContentDirectory):
raise NotADirectoryError(self.path)
current = current.__load_children(part)
return current
def glob(self, pattern: str) -> Iterable[Content]:
for item in self.path.glob(pattern):
yield self.load(str(item))
@cache # noqa: B019
def __load_children(self, name: str) -> Content:
child_path = self.__get_localized_path(self.path / name)
if not child_path.exists():
raise FileNotFoundError(child_path)
if child_path.is_dir():
return ContentDirectory(child_path)
if child_path.is_file():
if child_path.suffix in [".yml", ".yaml", ".json"]:
return DataFile(child_path)
if child_path.suffix == ".md":
return MarkdownFile(child_path)
raise NotImplementedError()
@staticmethod
def __get_localized_path(path: Path) -> Path:
domain = textdomain()
localized_path = path.with_name(f"{path.stem}-{domain}{path.suffix}")
if not localized_path.exists():
return path
return localized_path
class DataField:
def __init__(self, file_path: Path, value: Any) -> None:
self.__file_path = file_path
self.__value = value
def as_path(self) -> Path:
return self.__file_path.parent / str(self.__value)
def __str__(self) -> str:
return str(self.__value)
def __getitem__(self, key: Any) -> Any:
return DataField(self.__file_path, self.__value.get(key))
def __iter__(self) -> Iterator[Any]:
for it in self.__value:
yield DataField(self.__file_path, it)
class DataFile(Content):
def __init__(self, path: Path) -> None:
super().__init__(path)
with path.open("r", encoding="utf-8") as data_file:
self.__data = load(data_file, Loader)
def __getitem__(self, key: Any) -> Any:
return DataField(self.path, self.__data.get(key))
def __iter__(self) -> Iterator[Any]:
for it in self.__data:
yield DataField(self.path, it)
class MarkdownFile(Content):
def __init__(self, path: Path) -> None:
super().__init__(path)
with path.open("r", encoding="utf-8") as markdown_file:
self.__markdown = Markdown(extensions=["full_yaml_metadata"])
self.__html = self.__markdown.convert(markdown_file.read())
@property
def html(self) -> str:
return self.__html
@property
def meta(self) -> Any:
return DataField(self.path, self.__markdown.Meta) # type: ignore