Files

112 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Build the docs Markdown files into a static GitHub Pages site.
This intentionally avoids themed site generators and template files. Each Markdown
file is converted to a minimal standalone HTML page, and relative .md links are
rewritten to the generated .html filenames.
"""
from __future__ import annotations
import html
import re
import shutil
from pathlib import Path, PurePosixPath
from urllib.parse import urlsplit, urlunsplit
import markdown
ROOT = Path(__file__).resolve().parents[2]
DOCS_DIR = ROOT / "docs"
SITE_DIR = ROOT / "site"
MARKDOWN_EXTENSIONS = ["fenced_code", "tables", "toc"]
HREF_RE = re.compile(r'href="([^"]+)"')
def output_path(source: Path) -> Path:
if source.name == "README.md":
return SITE_DIR / "index.html"
return SITE_DIR / f"{source.stem}.html"
def page_title(text: str, fallback: str) -> str:
for line in text.splitlines():
if line.startswith("# "):
return line[2:].strip()
return fallback
def rewrite_markdown_links(rendered: str) -> str:
def replace(match: re.Match[str]) -> str:
href = html.unescape(match.group(1))
parts = urlsplit(href)
if parts.scheme or parts.netloc or not parts.path.endswith(".md"):
return match.group(0)
url_path = PurePosixPath(parts.path)
if url_path.name == "README.md":
new_path = str(url_path.with_name("index.html"))
else:
new_path = parts.path[:-3] + ".html"
new_href = urlunsplit(("", "", new_path, parts.query, parts.fragment))
return f'href="{html.escape(new_href, quote=True)}"'
return HREF_RE.sub(replace, rendered)
def render_page(source: Path) -> str:
text = source.read_text(encoding="utf-8")
title = page_title(text, "Cassady docs")
body = markdown.markdown(
text,
extensions=MARKDOWN_EXTENSIONS,
output_format="html5",
)
body = rewrite_markdown_links(body)
return "\n".join(
[
"<!doctype html>",
'<html lang="en">',
"<head>",
' <meta charset="utf-8">',
' <meta name="viewport" content="width=device-width, initial-scale=1">',
f" <title>{html.escape(title)}</title>",
"</head>",
"<body>",
body,
"</body>",
"</html>",
"",
]
)
def copy_static_assets() -> None:
for item in DOCS_DIR.iterdir():
if item.suffix == ".md":
continue
destination = SITE_DIR / item.name
if item.is_dir():
shutil.copytree(item, destination)
elif item.is_file():
shutil.copy2(item, destination)
def main() -> None:
if SITE_DIR.exists():
shutil.rmtree(SITE_DIR)
SITE_DIR.mkdir(parents=True)
for source in sorted(DOCS_DIR.glob("*.md")):
output_path(source).write_text(render_page(source), encoding="utf-8")
copy_static_assets()
(SITE_DIR / ".nojekyll").write_text("", encoding="utf-8")
if __name__ == "__main__":
main()