#!/usr/bin/env python3 """Drive the TUI in a pty and print each screen with ANSI stripped. Usage: scripts/tui-tour.py [--root DIR] [--width N] [--height N] [--make-fixtures] """ import argparse import fcntl import os import pty import re import select import struct import sys import termios import time ANSI = re.compile(r"\x1b\[[0-9;?]*[a-zA-Z]") OSC = re.compile(r"\x1b\][^\x07]*\x07") CHARSET = re.compile(r"\x1b[()][A-Z0-9]") def clean(data: bytes) -> list[str]: """Strip escapes and keep the last paint of each line.""" text = CHARSET.sub("", OSC.sub("", ANSI.sub("", data.decode("utf-8", "replace")))) out: list[str] = [] for line in text.replace("\r\n", "\n").replace("\r", "\n").split("\n"): line = line.rstrip() if line.strip() and (not out or out[-1] != line): out.append(line) return out class Session: def __init__(self, args): self.args = args self.pid, self.fd = pty.fork() if self.pid == 0: os.environ["INTAKE_THEME"] = args.theme os.environ["TERM"] = "xterm-256color" os.execv(args.binary, [ args.binary, "tui", "--root", args.root, "--state-dir", os.path.join(args.root, "state"), ]) fcntl.ioctl(self.fd, termios.TIOCSWINSZ, struct.pack("HHHH", args.height, args.width, 0, 0)) def drain(self, seconds: float) -> bytes: out = b"" end = time.time() + seconds while time.time() < end: ready, _, _ = select.select([self.fd], [], [], 0.25) if ready: try: out += os.read(self.fd, 65536) except OSError: break return out def send(self, keys: bytes) -> bytes: os.write(self.fd, keys) return self.drain(self.args.wait) def close(self): try: os.write(self.fd, b"q") time.sleep(0.3) except OSError: pass def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--root", default="/tmp/intake-tour") ap.add_argument("--binary", default="./out/intake") ap.add_argument("--width", type=int, default=104) ap.add_argument("--height", type=int, default=34) ap.add_argument("--wait", type=float, default=3.0) ap.add_argument("--theme", default="dark") ap.add_argument("--make-fixtures", action="store_true", help="generate fixtures into /staging first") args = ap.parse_args() if args.make_fixtures: os.makedirs(os.path.join(args.root, "staging"), exist_ok=True) os.system(f"bash scripts/make-fixtures.sh {args.root}/staging >/dev/null 2>&1") s = Session(args) print(f"===== BROWSE ({args.width}x{args.height}) =====") print("\n".join(clean(s.drain(4)))) for label, keys in [ ("METADATA", b"\r"), ("TRACK NAMES", b"\r"), ("COVER ART", b"\r"), ("COVER PICKER", b"\r"), ]: print(f"\n===== {label} =====") print("\n".join(clean(s.send(keys)))) s.close() return 0 if __name__ == "__main__": sys.exit(main())