OXYGENHUB
# 2980b7b6d7cd enhanced-x16-emulator / tests / refs / fetch_refs.py
5.1 KiB · Python · last changed chore(): initial commit 25 days ago source blame history raw
1
#!/usr/bin/env python3
2
"""Fetch the reference documents the conformance tests are written against.
3
4
The tests assert hardware behaviour, so every non-obvious expectation should be
5
traceable to a document rather than to somebody's memory. This pulls those
6
documents down and turns them into greppable text with page markers, so a test
7
comment can cite a page and the next person can check it.
8
9
    python tests/refs/fetch_refs.py
10
11
Nothing fetched here is committed. The datasheets are copyrighted by Western
12
Design Center and the tutorials are Bruce Clark's work; this only automates
13
downloading them to a local, ignored directory. Run it once, and again whenever
14
you want a fresh copy.
15
16
Text extraction needs pypdf (pip install pypdf). Without it the PDFs are still
17
downloaded and the HTML references still work; only the .txt files are skipped,
18
and a test that cites a page can still be checked against the PDF by hand.
19
20
The CPU tests already cite pages of the two WDC CPU datasheets. Until this
21
script was committed those citations named a document nobody could fetch,
22
which is the failure mode the whole "quote your oracle" rule exists to stop.
23
"""
24
25
import re
26
import sys
27
import urllib.parse
28
import urllib.request
29
from pathlib import Path
30
31
DEST = Path(__file__).resolve().parent / "downloads"
32
33
UA = {"User-Agent": "Mozilla/5.0 (x16-emulator test reference fetcher)"}
34
35
PDFS = {
36
    # Cited by the CPU conformance tests.
37
    "w65c816s.pdf": "https://www.westerndesigncenter.com/wdc/documentation/w65c816s.pdf",
38
    "w65c02s.pdf": "https://www.westerndesigncenter.com/wdc/documentation/w65c02s.pdf",
39
    # The VIA. Two of these sit at $9F00 and $9F10; the X16 uses them for the
40
    # PS/2 ports, the SD card select, the I2C bus and the RAM/ROM bank latches.
41
    "w65c22s.pdf": "https://www.westerndesigncenter.com/wdc/documentation/w65c22s.pdf",
42
}
43
44
PAGES = {
45
    "6502org-65c816-opcodes.txt": "http://6502.org/tutorials/65c816opcodes.html",
46
    "6502org-65c02-opcodes.txt": "http://6502.org/tutorials/65c02opcodes.html",
47
}
48
49
# The X16's own documentation. Unlike VERA there is no hardware definition to
50
# consult: the banking is discrete logic on the board, with no schematic or
51
# programmable-logic source published anywhere, so this is the strongest source
52
# there is for the memory map -- and it is documentation, a step removed.
53
#
54
# Pinned to a commit rather than a branch, for the same reason the VERA RTL is:
55
# a citation of a line in a moving file is not checkable. The Memory Map
56
# chapter carries its own warning that "the layout of the banks may still
57
# change", so this pin will need moving one day.
58
DOCS_COMMIT = "79a8303bc33cda4ec3e85f3239f9b3d4c8c76260"
59
60
DOCS = {
61
    "x16-memory-map.md": "X16 Reference - 08 - Memory Map.md",
62
    "x16-hardware.md": "X16 Reference - 14 - Hardware.md",
63
}
64
65
66
def fetch(url, path):
67
    if path.exists():
68
        print(f"  have {path.name}")
69
        return True
70
    print(f"  get  {path.name}")
71
    try:
72
        req = urllib.request.Request(url, headers=UA)
73
        with urllib.request.urlopen(req, timeout=120) as r:
74
            path.write_bytes(r.read())
75
        return True
76
    except Exception as exc:
77
        print(f"  FAIL {path.name}: {exc}")
78
        return False
79
80
81
def html_to_text(path):
82
    raw = path.read_bytes().decode("utf-8", "replace")
83
    raw = re.sub(r"(?is)<(script|style).*?</\1>", "", raw)
84
    raw = re.sub(r"(?i)<br\s*/?>", "\n", raw)
85
    raw = re.sub(r"(?i)</(p|div|tr|h\d|li)>", "\n", raw)
86
    text = re.sub(r"(?s)<[^>]+>", "", raw)
87
    for a, b in (("&lt;", "<"), ("&gt;", ">"), ("&amp;", "&"), ("&nbsp;", " ")):
88
        text = text.replace(a, b)
89
    return re.sub(r"\n{3,}", "\n\n", text)
90
91
92
def pdf_to_text(pdf, out):
93
    try:
94
        import pypdf
95
    except ImportError:
96
        print(f"  skip {out.name} (pip install pypdf to enable)")
97
        return
98
    reader = pypdf.PdfReader(str(pdf))
99
    with out.open("w", encoding="utf-8") as fh:
100
        for i, page in enumerate(reader.pages, 1):
101
            # The page marker is the point of this: a citation is only useful if
102
            # the next person can find the line again.
103
            fh.write(f"\n===== page {i} =====\n")
104
            fh.write(page.extract_text() or "")
105
    print(f"  made {out.name} ({len(reader.pages)} pages)")
106
107
108
def main():
109
    DEST.mkdir(parents=True, exist_ok=True)
110
    ok = True
111
112
    print("WDC datasheets:")
113
    for name, url in PDFS.items():
114
        path = DEST / name
115
        if fetch(url, path):
116
            pdf_to_text(path, path.with_suffix(".txt"))
117
        else:
118
            ok = False
119
120
    print("6502.org tutorials:")
121
    for name, url in PAGES.items():
122
        raw = DEST / (name + ".html")
123
        if fetch(url, raw):
124
            (DEST / name).write_text(html_to_text(raw), encoding="utf-8")
125
            raw.unlink()
126
            print(f"  made {name}")
127
        else:
128
            ok = False
129
130
    print("X16 documentation:")
131
    base = f"https://raw.githubusercontent.com/X16Community/x16-docs/{DOCS_COMMIT}"
132
    for name, path in DOCS.items():
133
        quoted = urllib.parse.quote(path)
134
        if not fetch(f"{base}/{quoted}", DEST / name):
135
            ok = False
136
137
    print(f"\nreferences in {DEST}")
138
    return 0 if ok else 1
139
140
141
if __name__ == "__main__":
142
    sys.exit(main())