aboutsummaryrefslogtreecommitdiff
path: root/tools/extract_missalemeum_oracle.py
blob: 044aa54c4c352e4ca97a55335dd8d2ecbe7d71ef (plain) (blame)
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
#!/usr/bin/env python3
# A documented one-shot, not part of the OCaml build (spec's "no hand-written
# JSON parser" rule is about colitur's OWN sexp format -- reading someone
# else's JSON needs a reader, same reasoning as tools/bootstrap_sanctoral.ml's
# own header comment on its INI reader). Task 16: extracts the missalemeum
# oracle fixture (test/fixtures/missalemeum-ef-2026-2027.txt) that
# test/test_oracle.ml compares colitur against -- there is no JSON library in
# this project's frozen deps, so the fixture is shaped here, once, outside
# the test, exactly as test/test_differential.ml's lectio fixture already is
# (see that file's own header comment).
#
# Usage:
#   python3 tools/extract_missalemeum_oracle.py <snapshot-dir> <out-file>
#
# <snapshot-dir> is ~/git/projects/lectio/sources/ already unpacked (`tar xzf
# snapshot.tar.gz` in that directory -- lectio's own scripts/snapshot-
# sources.sh does this), so <snapshot-dir>/missalemeum/en/YYYY-MM-DD.json
# exists for every day.
#
# One line per day, pipe-separated:
#   date|rank|colors|title|tempora|commemorations|displaced|n_masses
#
# - rank/colors/title/tempora/commemorations/displaced are info.rank,
#   info.colors (sorted, concatenated, e.g. "pv"), info.title, info.tempora
#   ("-" for JSON null), the ";"-joined titles of info.commemorations and
#   info.displaced ("-" for an empty list), verbatim from the JSON's own
#   English strings -- not translated or slugified, so the comparator (and a
#   human auditor) can match colitur's own celebration names against them.
# - n_masses is len(the day's JSON array). Four days (Christmas, All Souls)
#   carry more than one Mass; entry[0]'s info block is used throughout (rank/
#   colors/tempora/commemorations/displaced are IDENTICAL across every Mass
#   of the same day for all 730 days -- checked below, not assumed; see the
#   task report for why entry[0] loses nothing).
# - Spaces in tempora are turned to "_" (matching the "no field has an
#   internal space" convention test/fixtures/lectio-ef-2005-2050.txt already
#   uses, so this fixture can be read the same simple way -- split on '|',
#   not on whitespace, so title/commemoration/displaced text keeps its own
#   spaces; only tempora, which nothing in this project parses further than
#   pass-through display, is space-collapsed for a cheap column-count check).
import json
import os
import sys


def main():
    if len(sys.argv) != 3:
        print(f"usage: {sys.argv[0]} <snapshot-dir> <out-file>", file=sys.stderr)
        return 2
    snapshot_dir, out_path = sys.argv[1], sys.argv[2]
    src = os.path.join(snapshot_dir, "missalemeum", "en")
    rows = []
    for fname in sorted(os.listdir(src)):
        if not fname.endswith(".json"):
            continue
        date = fname[:-5]
        with open(os.path.join(src, fname), encoding="utf-8") as f:
            data = json.load(f)
        first = data[0]["info"]
        for other in data[1:]:
            o = other["info"]
            for k in ("rank", "colors", "tempora", "commemorations", "displaced"):
                if first[k] != o[k]:
                    print(f"WARNING: {date} masses disagree on {k}: {first[k]!r} vs {o[k]!r}", file=sys.stderr)
        comm_titles = [c["title"] for c in first["commemorations"]]
        disp_titles = [d["title"] for d in first["displaced"]]
        for t in [first["title"]] + comm_titles + disp_titles:
            if "|" in t or ";" in t:
                print(f"ERROR: {date}: field {t!r} contains a delimiter this fixture uses", file=sys.stderr)
                return 1
        colors = "".join(sorted(first["colors"]))
        tempora = (first["tempora"] or "-").replace(" ", "_")
        comms = ";".join(comm_titles) or "-"
        disp = ";".join(disp_titles) or "-"
        rows.append(f"{date}|{first['rank']}|{colors}|{first['title']}|{tempora}|{comms}|{disp}|{len(data)}")

    if len(rows) != 730:
        print(f"ERROR: expected 730 days, got {len(rows)}", file=sys.stderr)
        return 1

    with open(out_path, "w", encoding="utf-8") as f:
        f.write("\n".join(rows) + "\n")
    print(f"wrote {len(rows)} lines to {out_path}", file=sys.stderr)
    return 0


if __name__ == "__main__":
    sys.exit(main())