-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnormalize.py
More file actions
151 lines (121 loc) · 5.2 KB
/
Copy pathnormalize.py
File metadata and controls
151 lines (121 loc) · 5.2 KB
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""
Pre-process a trajectory that alternates
{"thought": "...", "action": "<bash command>"}
{"observation": "<stdout / stderr text>"}
The only network identifier that can appear is an IPv4 address,
optionally as part of ssh user@IP commands.
Output (per step):
{
"cwd" : "<ROOT>/…",
"stdout_prev": "...",
"stderr_prev": "...",
"cmd" : "<canonicalised command>"
}
"""
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from typing import Dict, Generator, List
# ──────────────────────────────────────────────
# CONFIG
# ──────────────────────────────────────────────
MAX_STDOUT = 10000
MAX_STDERR = 10000
REPO_ROOT_PLACEHOLDER = "<ROOT>"
HOME_PLACEHOLDER = "<HOME>"
TMP_PLACEHOLDER = "<TMP>"
SHA_PLACEHOLDER = "<SHA>"
DATE_PLACEHOLDER = "<DATE>"
IP_PLACEHOLDER = "<IP>"
SSH_PLACEHOLDER = "ssh <USER>@<IP>"
# absolute path of the repo that was used when recording the demos
# set to "" to disable
DEMO_REPO_ABS_PATH = str(Path("/your/demo/repo").resolve())
# ──────────────────────────────────────────────
# REGEXES
# ──────────────────────────────────────────────
RE_HOME = re.compile(r"/home/[^/\s]+")
RE_TMP = re.compile(r"/tmp/[^/\s]+")
RE_SHA = re.compile(r"\b[0-9a-f]{7,40}\b")
RE_DATE = re.compile(r"\b\d{4}-\d{2}-\d{2}\b")
# IPv4 (simplified: 1‒3 digits each byte)
RE_IPV4 = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")
# ssh user@IP
RE_SSH = re.compile(r"\bssh\s+([^\s@]+)@((?:\d{1,3}\.){3}\d{1,3})")
# ──────────────────────────────────────────────
def canonicalise(text: str) -> str:
"""Replace host-specific and machine-specific artefacts with placeholders."""
if not text:
return text
text = text.replace("\r\n", "\n")
if DEMO_REPO_ABS_PATH:
text = text.replace(DEMO_REPO_ABS_PATH, REPO_ROOT_PLACEHOLDER)
text = RE_HOME.sub(HOME_PLACEHOLDER, text)
text = RE_TMP.sub(TMP_PLACEHOLDER, text)
text = RE_SHA.sub(SHA_PLACEHOLDER, text)
text = RE_DATE.sub(DATE_PLACEHOLDER, text)
# ssh user@IP → ssh <USER>@<IP>
text = RE_SSH.sub(SSH_PLACEHOLDER, text)
# plain IP addresses
text = RE_IPV4.sub(IP_PLACEHOLDER, text)
return text
def norm_cd(current: str, arg: str) -> str:
"""Cheap string-only resolution for a cd command, no real IO."""
arg = arg.strip()
if not arg or arg == "-":
return current
if arg.startswith("~"):
arg = HOME_PLACEHOLDER + arg[1:]
p = Path(arg) if Path(arg).is_absolute() else Path(current) / arg
try:
return str(p.resolve())
except Exception:
return str(p)
def trim_tail(s: str, limit: int) -> str:
return s if len(s) <= limit else s[-limit:]
# ──────────────────────────────────────────────
def iter_steps(traj: List[Dict[str, str]]):
"""Yield cleaned steps one by one."""
cwd = REPO_ROOT_PLACEHOLDER
stdout_prev, stderr_prev = "", ""
it = iter(traj)
for action_rec, obs_rec in zip(it, it):
cmd = action_rec.get("action", "")
raw_out = obs_rec.get("observation", "")
# rudimentary stdout / stderr split; change if you have a delimiter
if "\nSTDERR:\n" in raw_out:
stdout_now, _, stderr_now = raw_out.partition("\nSTDERR:\n")
else:
stdout_now, stderr_now = raw_out, ""
yield {
"cwd" : canonicalise(cwd),
"stdout_prev": canonicalise(trim_tail(stdout_prev, MAX_STDOUT)),
"stderr_prev": canonicalise(trim_tail(stderr_prev, MAX_STDERR)),
"cmd" : canonicalise(cmd.strip()),
}
# update synthetic state for next step
if cmd.lstrip().startswith("cd "):
cwd = norm_cd(cwd, cmd.lstrip()[3:])
stdout_prev, stderr_prev = stdout_now, stderr_now
def process_trajectory(
traj: List[Dict[str, str]],
out_path: str | os.PathLike | None = None,
) -> List[Dict[str, str]]:
processed = list(iter_steps(traj))
if out_path:
with open(out_path, "w", encoding="utf-8") as fh:
for rec in processed:
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
return processed
# ──────────────────────────────────────────────
if __name__ == "__main__":
demo_traj = [
{"thought": "connect", "action": "ssh alice@172.18.0.3"},
{"observation": "Welcome to Ubuntu\n"},
{"thought": "run tests", "action": "pytest -q"},
{"observation": "F..\n=== FAILURES ===\nE AssertionError: ..."},
]
steps = process_trajectory(demo_traj)
print(json.dumps(steps, indent=2))