165 lines
5.0 KiB
Python
165 lines
5.0 KiB
Python
|
from __future__ import annotations
|
||
|
|
||
|
from dataclasses import dataclass, field
|
||
|
from pathlib import Path
|
||
|
from typing import Type
|
||
|
|
||
|
from frontmatter import parse
|
||
|
|
||
|
from ..layouts.base import BaseLayout
|
||
|
from ..layouts.home import HomeLayout
|
||
|
from ..layouts.main import MainLayout
|
||
|
from ..markdown import render_markdown
|
||
|
from .docobject import organize_docobjects
|
||
|
|
||
|
_PAGE_CACHE: dict[
|
||
|
str, dict[str, tuple[Page | None, Page | None, Page | None]]
|
||
|
] = {}
|
||
|
_LAYOUTS_CACHE: dict[str, Type[BaseLayout]] = {
|
||
|
"home": HomeLayout,
|
||
|
"main": MainLayout,
|
||
|
}
|
||
|
_DEFAULT = "en"
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class PageMeta:
|
||
|
language: str = _DEFAULT
|
||
|
title: str = ""
|
||
|
description: str = ""
|
||
|
layout: str = "main"
|
||
|
features: list[dict[str, str]] = field(default_factory=list)
|
||
|
|
||
|
|
||
|
@dataclass
|
||
|
class Page:
|
||
|
path: Path
|
||
|
content: str
|
||
|
meta: PageMeta = field(default_factory=PageMeta)
|
||
|
_relative_path: Path | None = None
|
||
|
next_page: Page | None = None
|
||
|
previous_page: Page | None = None
|
||
|
anchors: list[str] = field(default_factory=list)
|
||
|
|
||
|
DEFAULT_LANGUAGE = _DEFAULT
|
||
|
|
||
|
def get_layout(self) -> Type[BaseLayout]:
|
||
|
return _LAYOUTS_CACHE[self.meta.layout]
|
||
|
|
||
|
@property
|
||
|
def relative_path(self) -> Path:
|
||
|
if self._relative_path is None:
|
||
|
raise RuntimeError("Page not initialized")
|
||
|
return self._relative_path
|
||
|
|
||
|
@classmethod
|
||
|
def get(
|
||
|
cls, language: str, path: str
|
||
|
) -> tuple[Page | None, Page | None, Page | None]:
|
||
|
if path.endswith("/") or not path:
|
||
|
path += "index.html"
|
||
|
if not path.endswith(".md"):
|
||
|
path = path.removesuffix(".html") + ".md"
|
||
|
if language == "api":
|
||
|
path = f"/api/{path}"
|
||
|
return _PAGE_CACHE.get(language, {}).get(path, (None, None, None))
|
||
|
|
||
|
@classmethod
|
||
|
def load_pages(cls, base_path: Path, page_order: list[str]) -> list[Page]:
|
||
|
output: list[Page] = []
|
||
|
for path in base_path.glob("**/*.md"):
|
||
|
relative = path.relative_to(base_path)
|
||
|
language = relative.parts[0]
|
||
|
name = "/".join(relative.parts[1:])
|
||
|
page = cls._load_page(path)
|
||
|
output.append(page)
|
||
|
page._relative_path = relative
|
||
|
_PAGE_CACHE.setdefault(language, {})[name] = (
|
||
|
None,
|
||
|
page,
|
||
|
None,
|
||
|
)
|
||
|
_PAGE_CACHE["api"] = {}
|
||
|
for language, pages in _PAGE_CACHE.items():
|
||
|
for name, (_, current, _) in pages.items():
|
||
|
previous_page = None
|
||
|
next_page = None
|
||
|
try:
|
||
|
index = page_order.index(name)
|
||
|
except ValueError:
|
||
|
continue
|
||
|
try:
|
||
|
if index > 0:
|
||
|
previous_page = pages[page_order[index - 1]][1]
|
||
|
except KeyError:
|
||
|
pass
|
||
|
try:
|
||
|
if index < len(page_order) - 1:
|
||
|
next_page = pages[page_order[index + 1]][1]
|
||
|
except KeyError:
|
||
|
pass
|
||
|
pages[name] = (previous_page, current, next_page)
|
||
|
previous_page = None
|
||
|
next_page = None
|
||
|
|
||
|
api_pages = cls._load_api_pages()
|
||
|
filtered_order = [ref for ref in page_order if ref in api_pages]
|
||
|
for idx, ref in enumerate(filtered_order):
|
||
|
current_page = api_pages[ref]
|
||
|
previous_page = None
|
||
|
next_page = None
|
||
|
try:
|
||
|
if idx > 0:
|
||
|
previous_page = api_pages[filtered_order[idx - 1]]
|
||
|
except KeyError:
|
||
|
pass
|
||
|
try:
|
||
|
if idx < len(filtered_order) - 1:
|
||
|
next_page = api_pages[filtered_order[idx + 1]]
|
||
|
except KeyError:
|
||
|
pass
|
||
|
_PAGE_CACHE["api"][ref] = (previous_page, current_page, next_page)
|
||
|
|
||
|
return output
|
||
|
|
||
|
@staticmethod
|
||
|
def _load_page(path: Path) -> Page:
|
||
|
raw = path.read_text()
|
||
|
metadata, raw_content = parse(raw)
|
||
|
content = render_markdown(raw_content)
|
||
|
page = Page(
|
||
|
path=path,
|
||
|
content=content,
|
||
|
meta=PageMeta(**metadata),
|
||
|
)
|
||
|
if not page.meta.title:
|
||
|
page.meta.title = page.path.stem.replace("-", " ").title()
|
||
|
|
||
|
for line in raw.splitlines():
|
||
|
if line.startswith("##") and not line.startswith("###"):
|
||
|
line = line.lstrip("#").strip()
|
||
|
page.anchors.append(line)
|
||
|
|
||
|
return page
|
||
|
|
||
|
@staticmethod
|
||
|
def _load_api_pages() -> dict[str, Page]:
|
||
|
docstring_content = organize_docobjects("sanic")
|
||
|
output: dict[str, Page] = {}
|
||
|
|
||
|
for module, content in docstring_content.items():
|
||
|
path = Path(module)
|
||
|
page = Page(
|
||
|
path=path,
|
||
|
content=content,
|
||
|
meta=PageMeta(
|
||
|
title=path.stem,
|
||
|
description="",
|
||
|
layout="main",
|
||
|
),
|
||
|
)
|
||
|
page._relative_path = Path(f"./{module}")
|
||
|
output[module] = page
|
||
|
|
||
|
return output
|