summaryrefslogtreecommitdiff
path: root/tools/export_lectio_patch.py
blob: de8a208e320592ad7d4ec7d455a197cc4a25aefb (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
88
89
90
91
#!/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()