#!/usr/bin/env python """Idempotently download, extract, and serve a mirror of .""" import argparse import http.client import shlex import subprocess import sys import tarfile from pathlib import Path import requests from send2trash import send2trash HERE = Path(__file__).parent # Something other than the default 8000 used by python -m http.server PORT = "8004" def get_options(): """Parse command line options.""" parser = argparse.ArgumentParser( description="Idempotently download and serve Python docs from a local copy" ) parser.add_argument( "--systemd", action="store_true", help="Also create, enable, and start a systemd service", ) parser.add_argument( "--clean", action="store_true", help="Clean out past downloads and unit files (uninstall)", ) return parser.parse_args() def clean(): """Clean (trash) past downloads and unit files.""" print("Cleaning past downloads and unit files") trash_targets = [] if ( p := Path.home() / ".config" / "systemd" / "user" / "python-docs.service" ).exists(): cmd = ["systemctl", "--user", "disable", "--now", "python-docs.service"] print(f" {shlex.join(cmd)}") subprocess.run(cmd).check_returncode() trash_targets.append(p) trash_targets.extend(HERE.glob("python-*.*-docs*/")) trash_targets.extend(HERE.glob("python-*.*-docs*.tar.bz2")) for p in trash_targets: print(f" Trashing {p}") send2trash(trash_targets) def download_extract(): """Download and extract Python docs to `HERE`.""" major, minor, *_ = sys.version_info url = f"https://docs.python.org/{major}.{minor}/archives/python-{major}.{minor}-docs-html.tar.bz2" outfile = HERE / f"python-{major}.{minor}-docs-html.tar.bz2" print(f"Downloading {url}") if outfile.is_file(): print(f" Already exists: {outfile}") else: with requests.get(url) as r, open(outfile, "wb") as f: if r.status_code != 200: descr = http.client.responses[r.status_code] raise RuntimeError(f"{r.status_code = } ({descr})") f.write(r.content) print(f" Downloaded: {outfile} ({len(r.content)} bytes)") print(f"Extracting {outfile}") assert outfile.is_file() outdir = HERE / f"python-{major}.{minor}-docs-html" if (outdir / "index.html").is_file(): print(f" Already exists: {outdir}/index.html") else: with tarfile.open(outfile) as f: f.extractall(path=HERE, filter="data") # Extracting HERE creates outdir assert (outdir / "index.html").is_file() return outdir def define_user_service(outdir): """Create a systemd unit file to serve docs as a user service.""" print("Creating systemd unit file") if not Path("/run/systemd/system").exists(): raise ValueError("Host appears not to use systemd") dest = Path.home() / ".config" / "systemd" / "user" / "python-docs.service" dest.parent.mkdir(parents=True, exist_ok=True) cmd = shlex.join( [sys.executable, "-m", "http.server", "-d", str(outdir.absolute()), PORT] ) body = f"""\ # Autogenerated by {__file__} [Service] Type=simple Description=Serve Python docs on port {PORT} ExecStart={cmd} [Install] WantedBy=default.target """ if dest.is_file(): with open(dest) as f: contents = f.read() if contents == body: print(f" Already defined: {dest}") return with open(dest, "w") as f: f.write(body) print(f" Saved: {dest}") def enable_user_service(): """Start and enable the systemd unit service.""" print("Enabling and starting systemd user service") cmd = ["systemctl", "--user", "daemon-reload"] print(f" {shlex.join(cmd)}") subprocess.run(cmd).check_returncode() cmd = ["systemctl", "--user", "enable", "--now", "python-docs"] print(f" {shlex.join(cmd)}") subprocess.run(cmd).check_returncode() if __name__ == "__main__": options = get_options() if options.clean: clean() exit() outdir = download_extract() if options.systemd: define_user_service(outdir) enable_user_service()