diff --git a/CLAUDE.md b/AGENTS.md similarity index 100% rename from CLAUDE.md rename to AGENTS.md diff --git a/mise-tasks/docs-preview b/mise-tasks/docs-preview new file mode 100755 index 0000000..6eb6bc6 --- /dev/null +++ b/mise-tasks/docs-preview @@ -0,0 +1,83 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.12" +# dependencies = ["rich>=13.0.0", "typer>=0.24.0"] +# /// +#MISE description="Serve a docs release tarball locally in a browser" +#USAGE arg "" help="Path to docs tarball (e.g. ~/Downloads/docs-v0.0.2.tar.gz)" +#USAGE flag "--port " default="8484" help="Port for preview server (default 8484)" +"""Extract a docs release tarball and serve it locally. + +Downloads the docs tarball from a Forgejo release page manually, then +point this task at it to preview the site in your browser. + +Usage: + mise run docs-preview ~/Downloads/docs-v0.0.2.tar.gz + mise run docs-preview ~/Downloads/docs-v0.0.2.tar.gz --port 9090 +""" + +import http.server +import tarfile +import tempfile +import threading +import webbrowser +from pathlib import Path +from typing import Annotated + +import typer +from rich.console import Console + +console = Console() + + +def main( + tarball: Annotated[Path, typer.Argument(help="Path to docs tarball")], + port: Annotated[int, typer.Option(help="Port for preview server")] = 8484, +) -> None: + tarball = tarball.expanduser().resolve() + if not tarball.exists(): + console.print(f"[bold red]File not found:[/bold red] {tarball}") + raise typer.Exit(code=1) + + docroot = Path(tempfile.mkdtemp(prefix="docs-preview-")) + console.print(f"[dim]Extracting {tarball.name} to {docroot}...[/dim]") + with tarfile.open(tarball, "r:gz") as tf: + tf.extractall(docroot, filter="data") + + url = f"http://localhost:{port}" + console.print(f"\n[bold green]Serving docs at {url}[/bold green]") + console.print(f"[yellow]Press Ctrl+C to stop.[/yellow]\n") + + threading.Timer(0.5, lambda: webbrowser.open(url)).start() + + class QuartzHandler(http.server.SimpleHTTPRequestHandler): + """Handler that resolves clean URLs to index.html files.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, directory=str(docroot), **kwargs) + + def do_GET(self): + # Quartz outputs foo.html, not foo/index.html, so clean URLs + # like /tutorials/tutorials need to resolve to tutorials.html + path = self.path.split("?")[0].split("#")[0] + file = docroot / path.lstrip("/") + if not file.suffix and not file.is_file(): + html_candidate = file.with_suffix(".html") + if html_candidate.is_file(): + self.path = path + ".html" + elif (file / "index.html").is_file(): + self.path = path.rstrip("/") + "/index.html" + super().do_GET() + + handler = QuartzHandler + server = http.server.HTTPServer(("localhost", port), handler) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + finally: + console.print("\n[dim]Stopped.[/dim]") + + +if __name__ == "__main__": + typer.run(main)