summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/genlect-of-season-ferials-cr.py125
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()