"""
Provide the Documentation API.
"""
from contextlib import AsyncExitStack
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import final
from aiofiles.os import makedirs
from typing_extensions import override
from betty import serve, fs
from betty.fs import ROOT_DIRECTORY_PATH
from betty.locale.localizer import Localizer
from betty.os import copy_tree
from betty.serve import Server, NoPublicUrlBecauseServerNotStartedError
from betty.subprocess import run_process
async def _prebuild_documentation() -> None:
await _build(fs.PREBUILT_ASSETS_DIRECTORY_PATH / "documentation")
async def _ensure_documentation_directory(cache_directory_path: Path) -> Path:
if (fs.PREBUILT_ASSETS_DIRECTORY_PATH / "documentation").exists():
return fs.PREBUILT_ASSETS_DIRECTORY_PATH / "documentation"
cache_directory_path /= "documentation"
if not cache_directory_path.exists():
await _build(cache_directory_path)
return cache_directory_path
async def _build(output_directory_path: Path) -> None:
await makedirs(output_directory_path, exist_ok=True)
with TemporaryDirectory() as working_directory_path_str:
working_directory_path = Path(working_directory_path_str)
# sphinx-apidoc must output to the documentation directory, but because we do not want
# to 'pollute' that with generated files that must not be committed, do our work in a
# temporary directory and copy the documentation source files there.
source_directory_path = working_directory_path / "source"
await copy_tree(ROOT_DIRECTORY_PATH / "documentation", source_directory_path)
await run_process(
[
"sphinx-apidoc",
"--force",
"--separate",
"-d",
"999",
"-o",
str(source_directory_path),
str(ROOT_DIRECTORY_PATH / "betty"),
str(ROOT_DIRECTORY_PATH / "betty" / "tests"),
],
cwd=working_directory_path,
)
await run_process(
[
"sphinx-build",
"-b",
"dirhtml",
"-j",
"auto",
str(source_directory_path),
str(output_directory_path),
],
cwd=working_directory_path,
)
[docs]
@final
class DocumentationServer(Server):
"""
Serve the documentation site.
"""
@override
@property
def public_url(self) -> str:
if self._server is not None:
return self._server.public_url
raise NoPublicUrlBecauseServerNotStartedError()