diff options
Diffstat (limited to 'scripts/genlect-of-season-ferials-cr.py')
| -rw-r--r-- | scripts/genlect-of-season-ferials-cr.py | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/scripts/genlect-of-season-ferials-cr.py b/scripts/genlect-of-season-ferials-cr.py new file mode 100644 index 0000000..13c56a3 --- /dev/null +++ b/scripts/genlect-of-season-ferials-cr.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +"""Re-source the Ordinary Form SEASONAL weekday (ferial) lectionary -- the proper +weekdays of Advent (weeks 1-3), Lent, and Eastertide -- from catholic-resources.org +(Fr. Felix Just, S.J.). These are single-cycle (the same every year), so both the +-I and -II weekday-cycle keys get identical readings. + +Fixes niedziela-harvest defects found by diffing: contaminated Easter weekday +first readings (easter-3-mon had 1 Cor 15 instead of Acts 6:8-15), the missing +Easter-octave weekdays, and stray psalm-numbering artifacts. The DATE-based days +(late Advent Dec 17-24 and the Christmas weekdays) are handled elsewhere. + +Scripture citations only; text rendered from the public-domain corpora. See NOTICE. +Requires network. python3 scripts/genlect-of-season-ferials-cr.py +""" +import re, sys, html, urllib.request + +INI = "internal/caldata/of-lectionary.ini" +PAGES = ["Weekdays-AdventChristmas", "Weekdays-Lent", "Weekdays-Easter"] +BASE = "https://www.catholic-resources.org/Lectionary/2002USL-{}.htm" +WD = {"m": "mon", "mon": "mon", "monday": "mon", "t": "tue", "tue": "tue", "tues": "tue", "tuesday": "tue", + "w": "wed", "wed": "wed", "wednesday": "wed", "th": "thu", "thu": "thu", "thurs": "thu", "thursday": "thu", + "f": "fri", "fri": "fri", "friday": "fri", "s": "sat", "sat": "sat", "saturday": "sat"} + +BOOK = {} +def reg(full, *al): + for a in al: BOOK[a.lower().replace(".", "").strip()] = full +reg("Genesis","gen");reg("Exodus","exod","ex");reg("Leviticus","lev");reg("Numbers","num") +reg("Deuteronomy","deut");reg("Joshua","josh");reg("Judges","judg");reg("Ruth","ruth") +reg("1 Samuel","1 sam");reg("2 Samuel","2 sam");reg("1 Kings","1 kgs");reg("2 Kings","2 kgs") +reg("1 Chronicles","1 chr");reg("2 Chronicles","2 chr");reg("Ezra","ezra");reg("Nehemiah","neh") +reg("Tobit","tob");reg("Judith","jdt");reg("Esther","esth");reg("1 Maccabees","1 macc");reg("2 Maccabees","2 macc","2 mac") +reg("Job","job");reg("Psalms","ps","pss");reg("Proverbs","prov");reg("Ecclesiastes","eccl") +reg("Song of Solomon","song","cant","songs");reg("Wisdom","wis");reg("Sirach","sir") +reg("Isaiah","isa","is");reg("Jeremiah","jer");reg("Lamentations","lam");reg("Baruch","bar") +reg("Ezekiel","ezek","ez");reg("Daniel","dan");reg("Hosea","hos");reg("Joel","joel");reg("Amos","amos") +reg("Obadiah","obad");reg("Jonah","jonah","jon");reg("Micah","mic","micah");reg("Nahum","nah");reg("Habakkuk","hab") +reg("Zephaniah","zeph");reg("Haggai","hag");reg("Zechariah","zech","zac");reg("Malachi","mal") +reg("Matthew","matt","mat","mt");reg("Mark","mark");reg("Luke","luke");reg("John","john","jn") +reg("The Acts","acts","act");reg("Romans","rom");reg("1 Corinthians","1 cor");reg("2 Corinthians","2 cor") +reg("Galatians","gal");reg("Ephesians","eph");reg("Philippians","phil");reg("Colossians","col") +reg("1 Thessalonians","1 thess");reg("2 Thessalonians","2 thess");reg("1 Timothy","1 tim");reg("2 Timothy","2 tim") +reg("Titus","titus");reg("Philemon","phlm");reg("Hebrews","heb","hebr");reg("James","jas") +reg("1 Peter","1 pet","1 petr");reg("2 Peter","2 pet");reg("1 John","1 john");reg("2 John","2 john");reg("3 John","3 john") +reg("Jude","jude");reg("Revelation","rev","apoc") +for _f in list(set(BOOK.values())): BOOK.setdefault(_f.lower(), _f) + +def st(s): + return html.unescape(re.sub(r"<[^>]+>", "", s)).replace("\xa0", " ").replace("\n", " ").replace("\r", " ").strip() + +def clean_cite(raw): + s = st(raw) + s = re.sub(r"[†‡*]", "", s) # dagger/star alternate-reading markers + s = re.sub(r"[–—]", "-", s) + s = re.sub(r"\([^)]*\)", "", s) + s = re.sub(r"\s+and\s+.*", "", s) # Ash Wed lists 2nd reading after "and" -> drop for first + s = re.split(r"\s+or\b", s, maxsplit=1)[0] + s = re.sub(r"^(cf\.?|see)\s+", "", s.strip(), flags=re.I).strip().strip(";,").strip() + m = re.match(r"((?:[1-4]\s+)?[A-Za-z][A-Za-z]*)\.?\s*(.*)", s) + if not m: return "" + full = BOOK.get(m.group(1).lower().strip()) + if not full: + sys.stderr.write(f" ! unknown book {m.group(1)!r} in {raw!r}\n"); return "" + return f"{full} {re.sub(r',\s+', ',', m.group(2)).replace(' ', '')}".strip() + +def slug_of(day): + d = re.sub(r"\s+", " ", st(day).lower()) + m = re.match(r"december (\d+)", d) # late Advent Dec 17-24: proper to the date + if m and 17 <= int(m.group(1)) <= 24: return "advent-dec-" + m.group(1) + if "ash wednesday" in d: return "lent-after-ashes-wed" + m = re.match(r"(thursday|friday|saturday) after ash", d) + if m: return "lent-after-ashes-" + WD[m.group(1)] + m = re.search(r"octave of easter\s*[-–—]\s*(\w+)", d) + if m and m.group(1) in WD: return "easter-octave-" + WD[m.group(1)] + m = re.search(r"(\d+)\w* week of (advent|lent|easter)\s*[-–—]\s*(\w+)", d) + if m and m.group(3) in WD: return f"{m.group(2)}-{m.group(1)}-" + WD[m.group(3)] + return None # date-based (Dec 17-24, Christmas weekdays): handled by the temporal engine, not here + +def firstbook(c): + m = re.match(r"((?:[1-4] )?[A-Za-z ]+?) (\d+)", c or "") + return f"{m.group(1)} {m.group(2)}" if m else (c or "") + +def main(): + cr = {} + for p in PAGES: + req = urllib.request.Request(BASE.format(p), headers={"User-Agent": "Mozilla/5.0 (X11; Linux) lectio"}) + h = urllib.request.urlopen(req, timeout=60).read().decode("utf-8", "replace") + n = 0 + for tr in re.split(r"<tr\b", h): + c = [x for x in re.findall(r"<td\b[^>]*>(.*?)</td>", tr, re.S)] + if len(c) < 6: continue + slug = slug_of(c[2]) + if not slug: continue + cr[slug] = {"first": clean_cite(c[3]), "psalm": clean_cite(c[4]), "gospel": clean_cite(c[-1])} + n += 1 + sys.stderr.write(f"{p}: {n} week-based seasonal ferials\n") + + text = open(INI).read() + head = text[:text.index("\n[")] + blocks, order = {}, [] + for b in re.split(r"\n(?=\[)", text[text.index("\n[") + 1:]): + k = re.match(r"\[(.+?)\]", b) + key = k.group(1) if k else b + blocks[key] = b.rstrip("\n"); order.append(key) + + changed = firstdiff = added = 0 + for slug, p in cr.items(): + if not (p["first"] and p["gospel"]): + continue + for cyc in ("I", "II"): # seasonal ferials are single-cycle + key = f"{slug}-{cyc}" + old = dict(re.findall(r"^(\w+)\s*=\s*(.+)$", blocks.get(key, ""), re.M)) + if cyc == "I" and old.get("first") and firstbook(old["first"]) != firstbook(p["first"]): + firstdiff += 1 + sys.stderr.write(f" ~ {slug}: first {old['first']!r} -> {p['first']!r}\n") + block = f"[{key}]\n" + "\n".join(f"{part} = {p[part]}" for part in ("first", "psalm", "gospel") if p[part]) + if key not in blocks: + order.append(key); added += 1 + else: + changed += 1 + blocks[key] = block + open(INI, "w").write(head.rstrip("\n") + "\n\n" + "\n\n".join(blocks[k] for k in order) + "\n") + sys.stderr.write(f"\nseasonal ferials: {changed} replaced, {added} new, {firstdiff} first-book changes\n") + +if __name__ == "__main__": + main() |
