project-template/mise-tasks/docs-preview

83 lines
2.8 KiB
Text
Raw Permalink Normal View History

#!/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 "<tarball>" help="Path to docs tarball (e.g. ~/Downloads/docs-v0.0.2.tar.gz)"
#USAGE flag "--port <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)