jean-web/jwebsite/content.py

99 lines
2.7 KiB
Python

from functools import cache
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.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()
class DataField:
def __init__(self, file: "DataFile", value: Any) -> None:
self.__file = file
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, self.__value.get(key))
def __iter__(self) -> Iterator[Any]:
for it in self.__value:
yield DataField(self.__file, 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, self.__data.get(key))
def __iter__(self) -> Iterator[Any]:
for it in self.__data:
yield DataField(self, 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 self.__markdown.Meta # type: ignore