aboutsummaryrefslogtreecommitdiffstats
path: root/godocs.py
diff options
context:
space:
mode:
Diffstat (limited to 'godocs.py')
-rw-r--r--godocs.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/godocs.py b/godocs.py
new file mode 100644
index 0000000..554fae9
--- /dev/null
+++ b/godocs.py
@@ -0,0 +1,122 @@
+from collections.abc import Iterator, Callable
+from pathlib import Path
+from dataclasses import dataclass
+import click
+import os
+from urllib.parse import quote
+from string import capwords
+import shutil
+import tempfile
+
+DRY = False
+
+@dataclass
+class Page:
+ title: str
+ url: str
+ entries: list[Path]
+ output: Path
+
+ @staticmethod
+ def create(source: Path, entries: list[Path], site_url: str, output_dir: Path):
+ title = capwords(source.stem.replace("-", " "))
+ url = f"{site_url}/{quote(source.stem)}"
+ output = output_dir / source.stem / "index.html"
+ return Page(title, url, entries, output)
+
+def renderPageCallback(template_file: Path, stylesheet_url: str) -> Callable[[Page, str], None]:
+ """ Callback to process the provided page metadata and output the final page to the filesystem """
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
+
+ env = Environment(
+ loader=FileSystemLoader(template_file.parent),
+ autoescape=select_autoescape(),
+ extensions=["jinja_markdown.MarkdownExtension"]
+ )
+ template = env.get_template(template_file.name)
+
+ def entries(page: Page) -> Iterator[str]:
+ for entry in page.entries:
+ with open(entry, "r") as f:
+ yield f.read()
+
+ def render(page: Page, site_title: str, navigation: list[dict[str, str]]) -> None:
+ print(f"Rendering {page.output}")
+ args = {"site_title": site_title, "stylesheet_url": stylesheet_url, "navigation": navigation, "entries": entries(page)}
+ content = template.render(args)
+ if DRY:
+ print(content)
+ else:
+ os.makedirs(page.output.parent, exist_ok=True)
+ with open(page.output, "w") as f:
+ f.write(content)
+
+ return render
+
+def collectPages(site_root: Path, site_url: str, output_dir: Path) -> Iterator[Page]:
+ """ Walk the site root collecting all the entries and organizing them into pages """
+ for f in os.listdir(site_root):
+ page_path = site_root / f
+
+ # Directories are rendered as one page with multiple entries
+ if os.path.isdir(page_path):
+ yield Page.create(page_path, list(page_path.glob("**/*.md")), site_url, output_dir)
+
+ # Single .md files are rendered as single-entry pages
+ elif page_path.suffix == ".md":
+ yield Page.create(page_path, [page_path], site_url, output_dir)
+
+def generateSite(site_root: Path, base_url: str, output_dir: Path, render_page: Callable[[Page, str], None]) -> None:
+ """ Generate the site located at the specified root """
+ print(f"Generating site rooted at {site_root}")
+
+ site_url = f"{base_url}/{quote(site_root.stem)}"
+ site_dir = output_dir / site_root.stem
+ site_title = capwords(site_root.stem.replace("-", " "))
+
+ pages = [p for p in collectPages(site_root, site_url, site_dir)]
+ navigation = [{"text": p.title, "url": p.url} for p in pages]
+ for page in pages:
+ render_page(page, site_title, navigation)
+
+ home = Page("Home", site_url, [], site_dir / "index.html")
+ render_page(home, site_title, navigation)
+
+def generateSites(config: dict[str, str]) -> None:
+ """ Generate all sites according to the specified config """
+ docs_root = Path(config["docs_root"])
+ output_dir = Path(config["output_dir"])
+ template_file = Path(config["template_file"])
+ css_file = Path(config["css_file"])
+ base_url = config["base_url"]
+ stylesheet_url = f"{base_url}/{css_file.name}"
+
+ render_page = renderPageCallback(template_file, stylesheet_url)
+
+ with tempfile.TemporaryDirectory() as tmpdir:
+ for f in os.listdir(docs_root):
+ site = docs_root / f
+ if os.path.isdir(site):
+ generateSite(site, base_url, Path(tmpdir), render_page)
+
+ shutil.copyfile(css_file, Path(tmpdir) / css_file.name)
+
+ if output_dir.exists():
+ shutil.rmtree(output_dir)
+ shutil.move(tmpdir, output_dir)
+
+@click.command()
+@click.argument("config_file", nargs=1, type=click.Path())
+@click.option("--dry/--no-dry", default=False, help="Instead of generating the sites, print every step to stdout")
+def main(config_file, dry):
+ global DRY
+ DRY = dry
+
+ with open(config_file, "rb") as f:
+ import tomllib
+ config = tomllib.load(f)
+
+ generateSites(config)
+
+if __name__ == '__main__':
+ main()