#!/usr/bin/env python3 """Build or repair a project's panes, in a fixed order. bin/board # the project in the current folder bin/board --folder ~/src/x # or another one bin/board --replace # close this project's panes and build again Every page is a workspace with one pane, named after the page. The first workspace holds a shell: it is the pane that never exits, so the session cannot lose its first workspace to TUIOS cleanup. Nothing here holds a workspace number. Each page asks for its workspace by name at the moment it opens, because closing a pane deletes an emptied workspace and renumbers the rest. Run it twice and nothing is duplicated. """ import os import shlex import subprocess import time import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.join(HERE, "lib")) import harness as h # noqa: E402 import tuios # noqa: E402 import project as pj # noqa: E402 BIN = os.path.join(os.path.dirname(HERE), "bin", "harness") def parse(argv): opts = {"folder": os.getcwd(), "replace": "--replace" in argv, "reset": "--reset" in argv} for i, a in enumerate(argv): if a == "--folder" and i + 1 < len(argv): opts["folder"] = argv[i + 1] return opts def pages(cfg, folder): """The pages, in the order they must appear on the workspace rail.""" bin_path = BIN proj = cfg["paperclip"]["project"] task = pj.watch_task(cfg) # (pane name, workspace name, command). The workspace is what a person reads # on the rail, so it is capitalised and says what the page is for; the pane # keeps the program's own name. Written Workspace>pane when talking about it. task_pages = [ ("shell", "Shell", "%s %s; exec zsh -i" % ( shlex.quote(os.path.join(os.path.dirname(BIN), "shell-banner")), shlex.quote(folder))), ("board", "Board", "%s -screen board -project %s -folder %s" % (shlex.quote(bin_path), proj, shlex.quote(folder))), ("work", "Work", "%s -screen work -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))), ("agents", "Agents", "%s -screen agents -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))), ("reply", "Reply", "%s -screen reply -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))), ("new", "New task", "%s -screen new -project %s" % (shlex.quote(bin_path), proj)), ("project", "Project", "%s -screen project -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))), ("repair", "Repair", "%s -screen repair -folder %s" % (shlex.quote(bin_path), shlex.quote(folder))), ] # Every pane falls back to a shell, so a program that exits never leaves a # dead pane behind: it returns to a prompt in the project folder. pages = [(n, w, c if n == "shell" else c + "; exec zsh -i") for n, w, c in task_pages] return task, pages def main(): opts = parse(sys.argv[1:]) folder = os.path.abspath(os.path.expanduser(opts["folder"])) cfg = pj.load(folder) pj.check_machine(cfg) session = cfg["session"] tuios.require_session(session) os.environ["PAPERCLIP_HARNESS_SESSION"] = session if not os.path.isfile(BIN): raise h.Refused(2, "no harness binary at %s; build it with 'cd tui && go build -o ../bin/harness .'" % BIN) task, page_list = pages(cfg, folder) h.say("board: %s (session %s, folder %s)" % (cfg["paperclip"]["project"], session, folder)) h.say("watching: %s" % task) if opts["reset"]: # Empty the session first, then build. --replace alone can run out of # workspace slots on a half-built session and leave pages missing. h.say("reset: closing every pane in %s" % session) for w in tuios.windows(session): subprocess.run(["tuios", "close-window", w["window_id"], "-s", session], capture_output=True, text=True, timeout=30) for _ in range(30): if not tuios.windows(session): break time.sleep(0.5) for w in tuios.workspaces(session): subprocess.run(["tuios", "set-workspace-name", str(w["workspace"]), "", "-s", session], capture_output=True, text=True, timeout=20) h.say("reset: %s" % ("empty" if not tuios.windows(session) else "panes remain, building anyway")) if opts["replace"]: for name, _, _ in page_list: if tuios.close_pane(name, session): h.say(" closed %s" % name) for name, workspace, command in page_list: result, ident, ws = tuios.open_pane( name, workspace, folder, command, session, adopt_existing=(name == "shell"), # The shell pane is a shell whether we started it or TUIOS restored # it, so it is always adopted. The other seven must be running the # harness, or they are a prompt pretending to be a page. expect=None if name == "shell" else "harness", ) h.say(" %-9s %-8s workspace %s" % (name, result, ws)) for number, name in tuios.arrange([(p[0], p[1]) for p in page_list], session): h.say(" placed %-8s workspace %s" % (name, number)) in_use = [w for w in tuios.workspaces(session) if (w.get("window_count") or 0) > 0] h.say("workspaces in use: %d" % len(in_use)) return 0 if __name__ == "__main__": h.main_guard(main)