aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/extract_missalemeum_oracle.py87
1 files changed, 87 insertions, 0 deletions
diff --git a/tools/extract_missalemeum_oracle.py b/tools/extract_missalemeum_oracle.py
new file mode 100644
index 0000000..044aa54
--- /dev/null
+++ b/tools/extract_missalemeum_oracle.py
@@ -0,0 +1,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())