"""Run Typst watchers with readable, document-specific output.""" from pathlib import Path from queue import Empty, Queue import signal import subprocess import sys from threading import Thread def relay(process, label, messages): with process.stdout: for line in process.stdout: messages.put((label, line.rstrip())) messages.put((label, None)) def interrupted(signum, frame): raise KeyboardInterrupt def main(): separator = sys.argv.index("--") executable, *flags = sys.argv[1:separator] sources = sys.argv[separator + 1:] messages = Queue() processes = [] signal.signal(signal.SIGTERM, interrupted) try: for source in sources: process = subprocess.Popen( [executable, "watch", *flags, source, str(Path(source).with_suffix(".pdf"))], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding="utf-8", errors="replace", start_new_session=True, ) processes.append(process) Thread(target=relay, args=(process, source, messages), daemon=True).start() while True: try: label, line = messages.get(timeout=0.2) except Empty: continue if line is None: print(f"[{label}] Watch-Prozess unerwartet beendet.", file=sys.stderr) return 1 if line: print(f"[{label}] {line}", flush=True) except KeyboardInterrupt: print("\nÜberwachung beendet.", flush=True) return 0 except OSError as error: print(f"Watch konnte nicht gestartet werden: {error}", file=sys.stderr) return 1 finally: # Stop all watchers, including when startup fails partway through. for process in processes: if process.poll() is None: process.terminate() for process in processes: try: process.wait(timeout=3) except subprocess.TimeoutExpired: process.kill() process.wait() if __name__ == "__main__": sys.exit(main())