#!/usr/bin/env python3 """Emit the minimal set of field changes that bring lectio's EF data into agreement with colitur, each annotated with the rubric that justifies it. DELIBERATELY A PATCH, NOT A REGENERATION. colitur carries 205 of the 327 Polish names lectio ships, so regenerating lectio's ini from colitur would silently drop 122 of them. It also uses a different slug vocabulary for Passion/Holy week and a different Paschaltide week numbering. What colitur is authoritative for is the adjudicated FIELDS -- rank/status, colour, and the reading citations it has verified against the Missal -- and this emits only those. Every line of output names the colitur allow-list entry (C-class) carrying the citation, so a lectio reviewer can check the rubric rather than trust the tool. """ import re, sys, os COL = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) LEC = os.path.expanduser('~/git/projects/lectio/internal/caldata') def parse_ini(path): out, cur = {}, None for line in open(path, errors='replace'): s = line.strip() m = re.match(r'^\[([\w-]+)\]$', s) if m: cur = m.group(1); out[cur] = {}; continue if cur and '=' in s and not s.startswith((';', '#')): k, v = s.split('=', 1); out[cur][k.strip()] = v.strip() return out def colitur_sanctoral(): t = open(f'{COL}/data/ef/sanctoral.sexp').read() out = {} for m in re.finditer(r'\(\(slug ([\w-]+)\)(.*?)\(layer', t, re.S): slug, blk = m.group(1), m.group(2) g = lambda k: (re.search(r'\(' + k + r' (\w+)\)', blk) or [None, None])[1] out[slug] = {'rank': g('rank'), 'status': g('status'), 'colour': g('colour'), 'subject': g('subject')} return out def colitur_lectionary(): t = open(f'{COL}/data/ef/lectionary.sexp').read() out = {} for m in re.finditer(r'\(([a-z0-9-]+)\s*\(((?:\s*\(\(part \w+\) \(reference "[^"]*"\)\))+)\s*\)\)', t): p = dict(re.findall(r'\(part (\w+)\) \(reference "([^"]*)"\)', m.group(2))) out[m.group(1)] = {'first': p.get('First'), 'gospel': p.get('Gospel')} return out RANKMAP = {'Class1': 'class-1', 'Class2': 'class-2', 'Class3': 'class-3', 'Class4': 'class-4'} # why each field change is being made, keyed by slug WHY = { 'ubaldus': ('C38', 'Missal calendarium: "S. Ubaldi Ep. et Conf., III classis" -- a feast, not a commemoration'), 'didacus': ('C38', 'Missal calendarium: "S. Didaci Conf., III classis" -- a feast, not a commemoration'), 'vigil-of-st-lawrence': ('C37', 'RG 128: violet for vigils of II and III class outside Paschaltide'), 'vigil-of-the-assumption': ('C37', 'RG 128: violet for vigils of II and III class outside Paschaltide'), 'ef-advent-ember-sat': ('C27', 'lectio carries St Thomas the Apostle\'s Mass under this key; the real Advent Ember Saturday Mass is 2 Thess 2:1-8 / Luke 3:1-6'), 'ef-september-ember-sat': ('C28', 'lectio carries St Matthew\'s Mass under this key; the real September Ember Saturday Mass is Heb 9:2-12 / Luke 13:6-17'), } def main(): lec_cal = parse_ini(f'{LEC}/tridentine-calendar.ini') lec_lec = parse_ini(f'{LEC}/tridentine-lectionary.ini') col_cal, col_lec = colitur_sanctoral(), colitur_lectionary() changes = [] for slug in sorted(set(lec_cal) & set(col_cal)): l, c = lec_cal[slug], col_cal[slug] want_rank = 'commemoration' if c['status'] == 'Commemoration_only' else RANKMAP.get(c['rank']) if l.get('rank') != want_rank: changes.append(('tridentine-calendar.ini', slug, 'rank', l.get('rank'), want_rank)) if c['colour'] and l.get('colour', '').lower() != c['colour'].lower(): changes.append(('tridentine-calendar.ini', slug, 'colour', l.get('colour'), c['colour'].lower())) for slug in sorted(set(lec_lec) & set(col_lec)): for k in ('first', 'gospel'): if col_lec[slug].get(k) and lec_lec[slug].get(k) != col_lec[slug][k]: changes.append(('tridentine-lectionary.ini', slug, k, lec_lec[slug].get(k), col_lec[slug][k])) print(f"# {len(changes)} field changes bringing lectio's EF data into agreement with colitur") print(f"# Generated by colitur tools/export_lectio_patch.py -- review, do not apply blind.\n") cur = None for f, slug, key, old, new in changes: if f != cur: print(f"\n## {f}"); cur = f cid, why = WHY.get(slug, ('?', 'no citation recorded -- INVESTIGATE before applying')) print(f" [{slug}]") print(f" {key}: {old!r} -> {new!r}") print(f" why ({cid}): {why}") only_col = sorted(set(col_lec) - set(lec_lec)) print(f"\n## lectionary entries colitur has and lectio lacks: {len(only_col)}") print(" (additions, not corrections -- listed for review, not emitted as a patch)") for s in only_col[:8]: print(f" {s}") if len(only_col) > 8: print(f" ... and {len(only_col)-8} more") main()