#!/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)