aboutsummaryrefslogtreecommitdiffstats
path: root/web/recordings/demos/scripting.py
blob: a3a2ac8a4cfa769bdea1f79cc4c8bc7adf208fab (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env python3
"""scripting -- built to be driven.

Left pane: a script's-eye view of the control socket -- the commands are
typed, the replies printed are byte-for-byte what the socket answers, and
the session performs the verb as each line lands. Right pane: a program
(deploy.py) that drew status text and real buttons into its own frame with
one escape sequence; the pointer clicks [Approve] and the click arrives
back on the program's stdin, which is where it wanted it.
"""

import os
import shutil
import sys
import tempfile

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from castgen import ROOT, Recorder, out_path

ENV = {"PS1": "$ ", "ENV": "/dev/null"}

# Amber, not the house pink: each cast in the tour records under its own
# theme, so scrolling to the next step reads as a scene change.
THEME = os.path.join(ROOT, "contrib", "themes", "amber.kdl")

work = tempfile.mkdtemp(prefix="slosh-demo-")

# The left pane's shell: reads a line, answers like the socket does. The
# replies below are copied from the real api replies at record time.
with open(os.path.join(work, "sh-replay"), "w") as f:
    f.write(
        "printf '$ '\n"
        "while IFS= read -r l; do\n"
        '  case "$l" in\n'
        '    *\'"split"\'*) echo \'{"ok":true,"id":4}\' ;;\n'
        "    *set-name*) echo '{\"ok\":true}' ;;\n"
        "    *) : ;;\n"
        "  esac\n"
        "  printf '$ '\n"
        "done\n"
    )

# The right pane's program: asks for approval in its own frame.
with open(os.path.join(work, "deploy.py"), "w") as f:
    f.write(
        "import os, sys, tty\n"
        "w = sys.stdout.write\n"
        "w('\\033]5577;1;status;release 1.4.2 -- awaiting approval\\033\\\\')\n"
        "w('\\033]5577;1;buttons;approve:Approve;cancel:Cancel\\033\\\\')\n"
        "print('deploy: waiting for a human (or an agent)')\n"
        "sys.stdout.flush()\n"
        "tty.setcbreak(0)\n"
        "buf = ''\n"
        "while 'approve' not in buf:\n"
        "    buf += os.read(0, 64).decode('utf-8', 'replace')\n"
        "w('\\033]5577;1;status;approved\\033\\\\')\n"
        "print('deploy: approved -- rolling out')\n"
        "sys.stdout.flush()\n"
        "os.read(0, 64)\n"
    )

lay = os.path.join(work, "demo.layout")
with open(lay, "w") as f:
    f.write(
        'layout {\n  tab {\n    pane focus=true cwd="%s" command="sh sh-replay"\n'
        '    pane cwd="%s" command="python3 deploy.py"\n  }\n}\n' % (work, work)
    )

r = Recorder(
    ["/bin/sh"],
    cols=100,
    rows=28,
    title="built to be driven",
    config=THEME,
    env=ENV,
    layout=lay,
)

r.capture(force=True, settle_ms=150)
r.pause(1.2)

# One JSON object per line: split, from outside.
r.type('slosh cmd \'{"cmd":"split","dir":"rows"}\'')
r.enter(dt=0.3)
reply = r.api("split", dir="rows")
r.capture(dt=0.4, settle_ms=80)
r.pause(1.4)

# Name the new pane from outside, too: the title changes where it stands.
new_id = reply["id"]
r.type(
    'slosh cmd \'{"cmd":"set-name","target":"pane","id":%d,"name":"build"}\'' % new_id
)
r.enter(dt=0.3)
r.api("set-name", target="pane", id=new_id, name="build")
r.capture(dt=0.4, settle_ms=80)
r.pause(1.4)

# The program in the other pane asked its question in its own frame.
# Answer it with the pointer; the click lands on its stdin.
snap = r.snapshot()
hit = next(e for e in snap.hits if "approve" in e["action"])
r.move_to(hit["x"] + hit["w"] // 2, hit["y"], dur=0.9)
r.pause(0.6)
r.press()
r.release()
r.capture(dt=0.4, settle_ms=150)
r.pause(2.2)

r.save(out_path("scripting"), tail=2.0)
shutil.rmtree(work)