163 lines
9.6 KiB
Python
163 lines
9.6 KiB
Python
"""Scenario generator for the mplex differential matrix (taicpp/tests/mplex_matrix_exec.py).
|
|
|
|
usage: python3 -I mplex_matrix_gen.py <repo root> <out.json>
|
|
|
|
Command tasks: for every command in MPLEX_COMMAND_SPECS and every flag letter of its getopt spec,
|
|
steps are generated from value pools, grouped in chunks of CHUNK steps; each chunk runs from a
|
|
freshly built preset on both implementations. Key tasks: every default binding as real key bytes.
|
|
Placeholders {W} (work dir) are substituted by the executor.
|
|
"""
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
|
|
root, out = sys.argv[1], sys.argv[2]
|
|
spec = importlib.util.spec_from_file_location("tai_mod", root + "/tai.py")
|
|
tai = importlib.util.module_from_spec(spec)
|
|
sys.modules["tai_mod"] = tai
|
|
spec.loader.exec_module(tai)
|
|
|
|
CHUNK = 8
|
|
TARGETS = ["main", "main:1", "main:1.1", "%1", "@1", "$0", "+", "-", "!", "{last}", "{next}", ":.+", ":=0", "{top-left}",
|
|
"other", "bogus", "main:2", "main:2.{bottom-right}", ":", ":.", "=main", "main:", ":^", ":$", "{end}"]
|
|
POOLS = {
|
|
"t": TARGETS, "s": ["main", "other", "new1", "%1", "1", "main:1", "x"],
|
|
"b": ["b1", "buf", "nosuch"], "n": ["nm", "1 2"], "F": ["#{pane_id}", "#{session_name}:#{window_index}", "x#{bogus}y",
|
|
"#{?pane_active,A,B}", "#{window_flags}#{pane_index}"],
|
|
"f": ["1", "0", "#{pane_active}", "#{==:#{pane_index},1}"], "c": ["/tmp", "/nonexistent"],
|
|
"x": ["40", "10", "0", "abc", "200"], "y": ["10", "5", "0", "abc", "100"], "l": ["5", "50%", "1", "abc", "100%"],
|
|
"e": ["A=1", "bad"], "E": ["echo"], "N": ["2", "x"], "P": ["p"], "p": ["Q"], "I": ["init"], "T": ["prefix", "root", "copy-mode", "copy-mode-vi", "custom", "ttl"],
|
|
"d": ["1", "x"], "A": ["%1:on", "x"], "B": ["n:w:#{x}"], "C": ["80x24", "x"], "r": ["x"], "S": ["1"], "K": ["Up"], "O": ["name"], "k": ["x"],
|
|
}
|
|
POSITIONAL = {
|
|
"bind-key": [["F5", "display-message hi"], ["a", "bogus-cmd"], ["C-x", "split-window ; select-pane -L"], ["Zzz", "x"]],
|
|
"unbind-key": [[], ["F5"], ["C-b"], ["zz"]],
|
|
"set-option": [["status-position", "top"], ["mouse", "on"], ["base-index", "1"], ["history-limit", "5"], ["status", "off"],
|
|
["nonexistent", "1"], ["status-left", "[x]"], ["mode-keys", "vi"], ["status-style", "bg=red"], ["status", ""],
|
|
["prefix", "C-a"], ["synchronize-panes", "on"], ["remain-on-exit", "on"]],
|
|
"set-window-option": [["synchronize-panes", "on"], ["mode-keys", "vi"], ["x", "1"]],
|
|
"show-options": [[], ["status"], ["bogus"], ["prefix"]],
|
|
"show-window-options": [[], ["mode-keys"], ["bogus"]],
|
|
"send-keys": [["abc"], ["Enter"], ["C-c", "Up"], ["Space", "a"], ["bogusKey"], ["cancel"], ["cursor-up"], ["search-forward-text", "1"]],
|
|
"source-file": [["/nonexistent"], ["{W}/src.conf"]],
|
|
"new-window": [[], ["cat"], ["echo", "hi"]], "split-window": [[], ["cat"]], "new-session": [[], ["cat"]],
|
|
"run-shell": [["echo hi"], ["false"], ["echo #{pane_id}"], []], "if-shell": [["true", "display-message yes", "display-message no"], ["false", "display-message yes"],
|
|
["false", "display-message yes", "display-message no"]],
|
|
"command-prompt": [[], ["rename-window %1"]], "confirm-before": [["kill-pane"], ["bogus"]],
|
|
"find-window": [["bash"], ["zzz"], ["."]], "rename-session": [["newname"], ["a b"]], "rename-window": [["w new"], ["x"]],
|
|
"load-buffer": [["{W}/buf.txt"], ["/nonexistent"]], "save-buffer": [["{W}/out.txt"]],
|
|
"set-buffer": [["data"], [], ["a b c"]], "display-message": [[], ["hi #{pane_id}"], ["#{bogus}"]],
|
|
"display-panes": [[], ["display-message x"]], "resize-pane": [[], ["3"], ["x"]], "select-layout": [[], ["tiled"], ["even-vertical"], ["main-horizontal"], ["bogus"],
|
|
["main-vertical-mirrored"]],
|
|
"choose-tree": [[], ["x"]], "choose-buffer": [[], ["x"]], "choose-client": [[], ["x"]],
|
|
"list-keys": [[], ["F5"], ["C-b"]], "refresh-client": [[], ["x"]], "copy-mode": [[]], "attach-session": [[]],
|
|
"kill-session": [[]], "capture-pane": [[]],
|
|
}
|
|
SKIP_FLAG_VALUES_FOR = {"kill-server"}
|
|
LAST = {"kill-server"}
|
|
|
|
|
|
def cstr(name):
|
|
return name
|
|
|
|
|
|
def positional_sets(name, low, high):
|
|
if name in POSITIONAL:
|
|
return POSITIONAL[name]
|
|
if low == 0:
|
|
return [[]]
|
|
return [["x"] * low, ["1"] * low]
|
|
|
|
|
|
def flag_letters(spec_text):
|
|
letters, i = [], 0
|
|
while i < len(spec_text):
|
|
takes = i + 1 < len(spec_text) and spec_text[i + 1] == ":"
|
|
letters.append((spec_text[i], takes))
|
|
i += 2 if takes else 1
|
|
return letters
|
|
|
|
|
|
def steps_for(name, aliases, spec_text, low, high, reduced):
|
|
steps = []
|
|
pos = positional_sets(name, low, high)
|
|
if reduced:
|
|
pos = pos[:2]
|
|
for p in pos:
|
|
steps.append([name] + p)
|
|
for letter, takes in flag_letters(spec_text):
|
|
values = POOLS.get(letter, ["1", "x"]) if takes else [None]
|
|
if name in ("command-prompt",) and letter == "T":
|
|
values = ["command", "search", "target"]
|
|
if reduced:
|
|
values = values[:2]
|
|
for value in values:
|
|
flag = ["-" + letter] + ([value] if value is not None else [])
|
|
for p in pos[:3 if not reduced else 1]:
|
|
steps.append([name] + flag + p)
|
|
if not reduced:
|
|
# a few flag pairs
|
|
letters = [l for l, t in flag_letters(spec_text) if not t][:4]
|
|
for i in range(len(letters)):
|
|
for j in range(i + 1, len(letters)):
|
|
steps.append([name, "-" + letters[i], "-" + letters[j]] + pos[0])
|
|
if aliases:
|
|
steps.append([aliases[0]] + pos[0])
|
|
return steps
|
|
|
|
|
|
COPY_CMDS = ["cancel", "cursor-up", "cursor-down", "cursor-down-and-cancel", "cursor-left", "cursor-right", "start-of-line", "end-of-line",
|
|
"back-to-indentation", "top-line", "middle-line", "bottom-line", "history-top", "history-bottom", "page-up", "page-down",
|
|
"page-down-and-cancel", "halfpage-up", "halfpage-down", "halfpage-down-and-cancel", "scroll-up", "scroll-down", "scroll-down-and-cancel",
|
|
"scroll-middle", "scroll-top", "scroll-bottom", "cursor-centre-vertical", "cursor-centre-horizontal", "goto-line", "refresh-from-pane",
|
|
"toggle-position", "next-word", "next-word-end", "next-space", "next-space-end", "previous-word", "previous-space", "next-paragraph",
|
|
"previous-paragraph", "next-matching-bracket", "previous-matching-bracket", "jump-forward", "jump-backward", "jump-to-forward",
|
|
"jump-to-backward", "jump-again", "jump-reverse", "begin-selection", "stop-selection", "clear-selection", "select-line", "select-word",
|
|
"rectangle-toggle", "rectangle-on", "rectangle-off", "other-end", "set-mark", "jump-to-mark", "copy-selection", "copy-selection-and-cancel",
|
|
"copy-pipe", "copy-pipe-and-cancel", "append-selection", "append-selection-and-cancel", "copy-end-of-line", "copy-end-of-line-and-cancel",
|
|
"copy-pipe-end-of-line", "copy-pipe-end-of-line-and-cancel", "copy-line", "copy-line-and-cancel", "copy-pipe-line", "copy-pipe-line-and-cancel",
|
|
"search-forward", "search-backward", "search-forward-text", "search-backward-text", "search-forward-incremental",
|
|
"search-backward-incremental", "search-again", "search-reverse"]
|
|
ARGS = {"goto-line": ["5", "x"], "jump-forward": ["3"], "jump-backward": ["1"], "jump-to-forward": ["9"], "jump-to-backward": ["2"],
|
|
"search-forward": ["1"], "search-backward": ["9"], "search-forward-text": ["1"], "search-backward-text": ["5"],
|
|
"search-forward-incremental": ["=7", "+8"], "search-backward-incremental": ["=3"], "copy-pipe": ["cat >/dev/null"]}
|
|
|
|
|
|
def main():
|
|
tasks = []
|
|
presets = ["P0", "P1", "P2", "P3", "P4", "P5"]
|
|
for name, (aliases, spec_text, low, high) in tai.MPLEX_COMMAND_SPECS.items():
|
|
for preset in presets:
|
|
reduced = preset != "P3"
|
|
steps = steps_for(name, list(aliases), spec_text, low, high, reduced)
|
|
# drop steps that would hang a CLI or are pure noise
|
|
if name == "kill-server":
|
|
steps = [[name]]
|
|
chunks = [steps[i:i + CHUNK] for i in range(0, len(steps), CHUNK)] or [[]]
|
|
if reduced:
|
|
chunks = chunks[:1]
|
|
for number, chunk in enumerate(chunks):
|
|
if name == "kill-server" and preset != "P2":
|
|
continue
|
|
tasks.append({"id": "cmd:%s:%s:%d" % (name, preset, number), "kind": "cmd", "preset": preset, "steps": chunk})
|
|
# copy mode commands from the copy-mode presets (emacs P4, vi P5), eight per fresh chunk
|
|
for preset in ("P4", "P5"):
|
|
steps = []
|
|
for cmd in COPY_CMDS:
|
|
steps.append(["send-keys", "-X", cmd] + ARGS.get(cmd, []))
|
|
for number in range(0, len(steps), CHUNK):
|
|
tasks.append({"id": "copy:%s:%d" % (preset, number // CHUNK), "kind": "cmd", "preset": preset + "c", "steps": steps[number:number + CHUNK]})
|
|
# key bindings
|
|
for table, preset in (("prefix", "P2"), ("prefix", "P3"), ("copy-mode", "P4c"), ("copy-mode-vi", "P5c")):
|
|
for key_table, key, command, repeat, note in tai.MPLEX_DEFAULT_KEYS:
|
|
if key_table != table:
|
|
continue
|
|
tasks.append({"id": "key:%s:%s:%s" % (table, preset, key), "kind": "key", "preset": preset, "table": table, "key": key})
|
|
# extra key scenarios: root table rebinds, repeat, prefix2, escape-time
|
|
tasks.append({"id": "key:custom", "kind": "custom"})
|
|
json.dump(tasks, open(out, "w"))
|
|
print(len(tasks), "tasks", sum(len(t.get("steps", [])) for t in tasks), "steps")
|
|
|
|
|
|
main()
|