Skip to content

Commit 65d5402

Browse files
committed
push demo as gif
1 parent 103f4c8 commit 65d5402

4 files changed

Lines changed: 3567 additions & 0 deletions

File tree

docs/record-repl-demo.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Record the query-json REPL demo as an asciinema .cast file.
4+
5+
Spawns the REPL in a PTY with a minimal terminal emulator that responds
6+
to terminal capability queries (DA, DSR, DECRQM, Kitty graphics/keyboard).
7+
8+
Usage:
9+
asciinema rec --cols 100 --rows 40 \
10+
-c "python3 docs/record-repl-demo.py" \
11+
docs/repl-demo.cast
12+
"""
13+
14+
import os
15+
import pty
16+
import re
17+
import sys
18+
import time
19+
import select
20+
import struct
21+
import fcntl
22+
import termios
23+
import threading
24+
25+
TYPE_SPEED = 0.08
26+
27+
TERMINAL_RESPONSES = {
28+
b'\x1b[c': b'\x1b[?62;22c',
29+
b'\x1b[>0q': b'\x1bP>|xterm(388)\x1b\\',
30+
b'\x1b[?u': b'\x1b[?0u',
31+
}
32+
33+
def handle_decrqm(data):
34+
"""Respond to DECRQM (Request Mode) queries: ESC[?{n}$p -> ESC[?{n};0$y"""
35+
results = []
36+
for m in re.finditer(rb'\x1b\[\?(\d+)\$p', data):
37+
mode = m.group(1)
38+
results.append((m.start(), m.end(), b'\x1b[?' + mode + b';0$y'))
39+
return results
40+
41+
def handle_dsr(data):
42+
"""Respond to DSR (cursor position): ESC[6n -> ESC[1;1R"""
43+
results = []
44+
for m in re.finditer(rb'\x1b\[6n', data):
45+
results.append((m.start(), m.end(), b'\x1b[1;1R'))
46+
for m in re.finditer(rb'\x1b\[\?996n', data):
47+
results.append((m.start(), m.end(), b''))
48+
return results
49+
50+
def handle_kitty_graphics(data):
51+
"""Respond to Kitty graphics queries."""
52+
results = []
53+
for m in re.finditer(rb'\x1b_Gi=(\d+)[^\\]*\x1b\\\\', data):
54+
gid = m.group(1)
55+
results.append((m.start(), m.end(), b'\x1b_Gi=' + gid + b';ENOTSUP\x1b\\'))
56+
return results
57+
58+
def process_output(data, master_fd):
59+
"""Check output for terminal queries and send responses."""
60+
for pattern, response in TERMINAL_RESPONSES.items():
61+
if pattern in data:
62+
os.write(master_fd, response)
63+
64+
for _, _, response in handle_decrqm(data):
65+
if response:
66+
os.write(master_fd, response)
67+
for _, _, response in handle_dsr(data):
68+
if response:
69+
os.write(master_fd, response)
70+
for _, _, response in handle_kitty_graphics(data):
71+
if response:
72+
os.write(master_fd, response)
73+
74+
def set_pty_size(fd, rows, cols):
75+
winsize = struct.pack('HHHH', rows, cols, 0, 0)
76+
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
77+
78+
def type_text(master_fd, text, speed=TYPE_SPEED):
79+
for ch in text:
80+
os.write(master_fd, ch.encode())
81+
time.sleep(speed)
82+
83+
def backspace(master_fd, n, speed=TYPE_SPEED):
84+
for _ in range(n):
85+
os.write(master_fd, b'\x7f')
86+
time.sleep(speed)
87+
88+
def main():
89+
master, slave = pty.openpty()
90+
91+
set_pty_size(master, 40, 100)
92+
set_pty_size(slave, 40, 100)
93+
94+
pid = os.fork()
95+
96+
if pid == 0:
97+
os.close(master)
98+
os.setsid()
99+
fcntl.ioctl(slave, termios.TIOCSCTTY, 0)
100+
os.dup2(slave, 0)
101+
os.dup2(slave, 1)
102+
os.dup2(slave, 2)
103+
if slave > 2:
104+
os.close(slave)
105+
106+
os.environ['TERM'] = 'xterm-256color'
107+
os.execvp('query-json', ['query-json', '--repl', 'cli/test/mock.json'])
108+
else:
109+
os.close(slave)
110+
111+
running = True
112+
def relay_output():
113+
while running:
114+
try:
115+
r, _, _ = select.select([master], [], [], 0.1)
116+
if r:
117+
data = os.read(master, 16384)
118+
if data:
119+
process_output(data, master)
120+
os.write(1, data)
121+
else:
122+
break
123+
except OSError:
124+
break
125+
126+
relay = threading.Thread(target=relay_output, daemon=True)
127+
relay.start()
128+
129+
time.sleep(3)
130+
131+
backspace(master, 1)
132+
133+
type_text(master, 'keys')
134+
time.sleep(2)
135+
136+
backspace(master, 4)
137+
138+
type_text(master, '.second.store')
139+
time.sleep(2)
140+
141+
type_text(master, '.books')
142+
time.sleep(2)
143+
144+
type_text(master, '[0]')
145+
time.sleep(2)
146+
147+
backspace(master, 3)
148+
time.sleep(1)
149+
150+
type_text(master, ' | ma')
151+
time.sleep(2)
152+
153+
type_text(master, 'p')
154+
time.sleep(1)
155+
156+
type_text(master, '(.price * 77)')
157+
time.sleep(1)
158+
159+
backspace(master, 17)
160+
time.sleep(1)
161+
162+
type_text(master, ' filter(.price > 10)')
163+
time.sleep(2.5)
164+
165+
os.write(master, b'\x03')
166+
time.sleep(1)
167+
168+
running = False
169+
try:
170+
os.waitpid(pid, 0)
171+
except ChildProcessError:
172+
pass
173+
os.close(master)
174+
175+
if __name__ == '__main__':
176+
main()

0 commit comments

Comments
 (0)