Repository navigation
Expand file tree
/
Copy pathknowledge_porting.py
More file actions
executable file
·182 lines (160 loc) · 6.33 KB
/
Copy pathknowledge_porting.py
File metadata and controls
executable file
·182 lines (160 loc) · 6.33 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin/env python3
"""Project docs/ASM_VALIDATION_PROGRESS.md into docs/knowledge/porting/<section>.md.
A Porting Status concept in the knowledge bundle is a generated artifact and is never
hand edited: docs/knowledge/SCHEMA.md makes that a rule, and this script is the process
it names. One concept per section of the progress doc that is written as a table. The
sections written as bullet lists (SOURCES, 3DEXT) are not projected yet.
scripts/dev/knowledge_porting.py 3D # write docs/knowledge/porting/3d.md
scripts/dev/knowledge_porting.py 3D --check # exit 1 if the file on disk has drifted
Timestamps come from the progress doc's last commit rather than from the clock, so a
regeneration with no source change is byte-identical and --check means something.
"""
import datetime
import os
import re
import subprocess
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
PROGRESS = "docs/ASM_VALIDATION_PROGRESS.md"
OUT_DIR = "docs/knowledge/porting"
ACTOR = "process:knowledge-porting"
STALE_DAYS = 180
# The progress doc's legend, verbatim: these three words are the bundle's equivalence
# vocabulary until docs/knowledge/SCHEMA.md settles on another.
STATUS = {"x": "tested", "~": "partial", " ": "untested"}
SECTION = re.compile(r"^## ([A-Za-z0-9_]+)/")
ROW = re.compile(r"^\| \[([x~ ])\] \|")
def source_date():
"""Last commit that touched the progress doc, as a UTC datetime."""
out = subprocess.check_output(
["git", "log", "-1", "--format=%cI", "--", PROGRESS], cwd=ROOT, text=True
).strip()
return datetime.datetime.fromisoformat(out).astimezone(datetime.timezone.utc)
def iso(when):
return when.strftime("%Y-%m-%dT%H:%M:%SZ")
def cells(line):
"""Split a table row into its cells. Cells hold backticks but no pipes."""
return [p.strip() for p in line.strip().strip("|").split("|")]
def read_section(name):
rows = []
inside = False
with open(os.path.join(ROOT, PROGRESS), encoding="utf-8") as f:
for line in f:
m = SECTION.match(line)
if m:
inside = m.group(1) == name
continue
if inside and ROW.match(line):
c = cells(line)
if len(c) < 6:
sys.exit("%s: short row in section %s: %s" % (PROGRESS, name, line.rstrip()))
rows.append(
{
"status": STATUS[ROW.match(line).group(1)],
"asm": c[1].strip("`"),
"cpp": c[2].strip("`"),
"routines": c[3],
"description": c[4],
"notes": c[5],
}
)
if not rows:
sys.exit("%s: no table rows under a '## %s/' heading" % (PROGRESS, name))
return rows
def yaml_str(s):
return '"%s"' % s.replace("\\", "\\\\").replace('"', '\\"')
def render(name, rows, when):
lib = "LIB386/%s" % name
counts = {v: 0 for v in STATUS.values()}
for r in rows:
counts[r["status"]] += 1
out = [
"---",
"type: Porting Status",
"title: %s" % yaml_str("Porting status, %s" % lib),
"description: %s"
% yaml_str(
"Per-routine equivalence status of the %d ASM and C++ pairs under %s, "
"projected from ASM_VALIDATION_PROGRESS.md." % (len(rows), lib)
),
"status: stable",
"subsystem: %s" % name.lower(),
"generated: { by: %s, at: %s }" % (ACTOR, iso(when)),
"stale_after: %s" % iso(when + datetime.timedelta(days=STALE_DAYS)),
"sources:",
" - id: progress",
" resource: ../../ASM_VALIDATION_PROGRESS.md",
" title: ASM to C validation progress",
" last_modified: %s" % iso(when),
" - id: reference",
" resource: ../../ASM_TO_CPP_REFERENCE.md",
" title: ASM to C++ reference",
"---",
"",
"> GENERATED by %s (scripts/dev/knowledge_porting.py). Do not hand edit." % ACTOR,
"> Regenerate with `scripts/dev/knowledge_porting.py %s`." % name,
"",
"# Summary",
"",
"| `equivalence` | Pairs |",
"|---|---|",
]
for v in STATUS.values():
out.append("| `%s` | %d |" % (v, counts[v]))
out += [
"",
"The status is the progress doc's own legend, one word per routine, and the notes "
"are its notes verbatim.[^progress] ASM_TO_CPP_REFERENCE.md lists no LIB386 rows, "
"so it contributes nothing to this projection beyond the mistranslation classes a "
"reader should check a port against.[^reference]",
"",
"# Routines",
]
for r in rows:
out += [
"",
"## %s" % r["routines"].replace("`", ""),
"",
"| | |",
"|---|---|",
"| ASM | [LIB386/%s](../../../LIB386/%s) |" % (r["asm"], r["asm"]),
"| C++ | [LIB386/%s](../../../LIB386/%s) |" % (r["cpp"], r["cpp"]),
"| `equivalence` | `%s` |" % r["status"],
"| Description | %s |" % r["description"],
"",
r["notes"],
]
out += [
"",
"[^progress]: docs/ASM_VALIDATION_PROGRESS.md, the `%s/` table." % name,
'[^reference]: docs/ASM_TO_CPP_REFERENCE.md, "Known mistranslation classes".',
"",
]
return "\n".join(out)
def main(argv):
check = "--check" in argv
names = [a for a in argv if not a.startswith("--")]
if not names:
sys.exit(__doc__)
when = source_date()
drift = 0
for name in names:
text = render(name, read_section(name), when)
path = os.path.join(ROOT, OUT_DIR, "%s.md" % name.lower())
if check:
try:
with open(path, encoding="utf-8") as f:
on_disk = f.read()
except FileNotFoundError:
on_disk = None
if on_disk != text:
print("%s: drifted from %s; regenerate" % (os.path.relpath(path, ROOT), PROGRESS))
drift = 1
continue
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
f.write(text)
print("wrote %s" % os.path.relpath(path, ROOT))
return drift
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))