"""Create the lecture screenshots in docs/images/ without touching the desktop. Every screenshot is taken by a headless Edge or Chrome (no window, no mouse, no keyboard). Terminal screenshots render the real output of real commands. Usage (from the repository root, with the project's .venv active): python docs/images/capture.py 1 2 3 4 5 6 """ # Developer tool only (not shipped in the package): every command it runs is a fixed list # built from constants, sys.executable and local paths; it reads no external input. import html import os import re import shutil import subprocess import sys import tempfile import time import urllib.request from pathlib import Path ROOT = Path(__file__).resolve().parents[2] OUT = ROOT / "docs" / "images" SOURCES = OUT / "sources" BROWSERS = [ r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe", r"C:\Program Files\Google\Chrome\Application\chrome.exe", "microsoft-edge", "google-chrome", "chromium", ] PORT = 5057 APP = "flask_day_54.presentation.hello" TERMINAL_CSS = """ body{margin:0;background:#1e1e1e;font-family:Consolas,'DejaVu Sans Mono',monospace;color:#d4d4d4} .bar{background:#333;color:#ccc;padding:8px 14px;font-size:14px} pre{margin:0;padding:16px 18px;font-size:15px;line-height:1.45;white-space:pre-wrap} .p{color:#6a9955}.c{color:#9cdcfe} """ BROWSER_CSS = """ body{margin:0;background:#f3f3f3;font-family:Segoe UI,Arial,sans-serif} .tab{background:#dde3ea;padding:8px 14px;font-size:13px;color:#333} .url{background:#fff;margin:8px;padding:6px 12px;border:1px solid #bbb;border-radius:14px;font-size:14px} .page{background:#fff;height:150px;padding:16px;font-size:16px;font-family:Times New Roman,serif} """ def find_browser() -> str: for candidate in BROWSERS: found = shutil.which(candidate) or ( candidate if os.path.exists(candidate) else None ) if found: return found raise SystemExit("No Edge or Chrome found for headless screenshots") def shot(page: Path, png: Path, width: int, height: int) -> None: """Take a headless screenshot of a local page or a URL.""" target = page.as_uri() if isinstance(page, Path) else page profile = tempfile.mkdtemp(prefix="shot-") subprocess.run( # nosemgrep [ find_browser(), "--headless=new", "--disable-gpu", "--hide-scrollbars", f"--user-data-dir={profile}", f"--window-size={width},{height}", f"--screenshot={png}", "--virtual-time-budget=3000", target, ], check=True, capture_output=True, timeout=60, ) shutil.rmtree(profile, ignore_errors=True) print("wrote", png.relative_to(ROOT)) def run(cmd: list[str], cwd: Path, timeout: int = 30) -> str: done = subprocess.run( # nosemgrep cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout ) return (done.stdout + done.stderr).rstrip("\n") def terminal_page( title: str, entries: list[tuple[str, str]], png: Path, height: int ) -> None: """Render (command, output) pairs as a terminal and take the screenshot.""" lines = [] for command, output in entries: lines.append( f'$ {html.escape(command)}' ) if output: lines.append(html.escape(output)) page = ( f"" f"
"
+ "\n".join(lines)
+ ""
)
SOURCES.mkdir(exist_ok=True)
src = SOURCES / (png.stem + ".html")
src.write_text(page, encoding="utf-8")
shot(src, png, 900, height)
def stop(proc: subprocess.Popen) -> None:
"""Stop a server and its reloader child."""
if os.name == "nt":
subprocess.run( # nosemgrep
["taskkill", "/F", "/T", "/PID", str(proc.pid)], capture_output=True
)
else:
proc.kill()
proc.wait(timeout=10)
def python() -> str:
return sys.executable
def step1() -> None:
shot(SOURCES / "lecture-01.html", OUT / "lecture-01.png", 900, 400)
def start_server() -> subprocess.Popen:
proc = subprocess.Popen( # nosemgrep
[python(), "-m", "flask", "--app", APP, "run", "--port", str(PORT)],
cwd=ROOT,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
for _ in range(40):
try:
urllib.request.urlopen(f"http://127.0.0.1:{PORT}/", timeout=1).read()
return proc
except OSError:
time.sleep(0.25) # nosemgrep
proc.kill()
raise SystemExit("server did not start")
def step2() -> None:
proc = start_server()
try:
body = (
urllib.request.urlopen(f"http://127.0.0.1:{PORT}/", timeout=3)
.read()
.decode()
)
page = (
f""
f"