#!/usr/bin/env python3 """Add PROPER Mass readings to the Ordinary Form sanctoral (internal/caldata/roman-calendar.ini) from catholic-resources.org's Proper of Saints table (Fr. Felix Just, S.J.). Without this, an OF feast/solemnity or a proper-reading memorial (e.g. the Apostles' feasts, the Assumption, All Saints, St Barnabas, Our Lady of Sorrows, the Guardian Angels) has no readings of its own and falls back to the day's ferial. Only celebrations whose readings are marked PROPER (and every feast / solemnity) are imported; memorials that draw on a Common keep the ferial, which is the OF default. Scripture CITATIONS only; text is rendered from the public- domain corpora. See NOTICE. Requires network. From the repo root: python3 scripts/gen-of-sanctoral-readings.py Adds reading.first / reading.psalm / reading.second / reading.gospel lines to the matching entries in internal/caldata/roman-calendar.ini. """ import re, sys, html, urllib.request INI = "internal/caldata/roman-calendar.ini" URL = "https://www.catholic-resources.org/Lectionary/2002USL-Sanctoral.htm" MONS = {m: i + 1 for i, m in enumerate("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split())} # CR abbreviation -> lectio full book name (same table as genlect-of-cr.py). 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") reg("Job","job");reg("Psalms","ps","pss");reg("Proverbs","prov");reg("Ecclesiastes","eccl") reg("Song of Solomon","song","cant");reg("Wisdom","wis");reg("Sirach","sir") reg("Isaiah","isa");reg("Jeremiah","jer");reg("Lamentations","lam");reg("Baruch","bar") reg("Ezekiel","ezek");reg("Daniel","dan");reg("Hosea","hos");reg("Joel","joel");reg("Amos","amos") reg("Obadiah","obad");reg("Jonah","jonah");reg("Micah","mic","micah");reg("Nahum","nah");reg("Habakkuk","hab") reg("Zephaniah","zeph");reg("Haggai","hag");reg("Zechariah","zech");reg("Malachi","mal") reg("Matthew","matt","mat");reg("Mark","mark");reg("Luke","luke");reg("John","john") reg("The Acts","acts");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");reg("James","jas") reg("1 Peter","1 pet");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") def clean_cite(raw): s = html.unescape(raw or "") s = re.sub(r"[–—]", "-", s) s = re.sub(r"\(#[^)]*\)", "", s) # drop lectionary numbers s = re.sub(r"\bo?r\s+elsewhere.*", "", s, flags=re.I) # cycle-specific gospel "A: ... B: ... C: ..." -> take the A option m = re.search(r"\bA:\s*(.+?)(?:\s+B:|$)", s) if m: s = m.group(1) s = re.split(r"\s+or\b", s, maxsplit=1)[0] # first of alternative readings s = s.strip().strip(";,").strip() if not s or s in (".", "x") or s.startswith("[") or s.startswith("("): return "" m = re.match(r"((?:[1-4]\s+)?[A-Za-z][A-Za-z]*\.?)\s*(.*)", s) if not m: return "" tok = m.group(1).lower().replace(".", "").strip() full = BOOK.get(tok) if not full: sys.stderr.write(f" ! unknown book {m.group(1)!r} in {raw!r}\n") return "" verses = re.sub(r",\s+", ",", m.group(2)).replace(" ", "") return f"{full} {verses}".strip() def strip_tags(s): return html.unescape(re.sub(r"<[^>]+>", "", s)).replace("\xa0", " ").replace("\n", " ").strip() def scrape(): req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0 (X11; Linux) lectio"}) h = urllib.request.urlopen(req, timeout=60).read().decode("utf-8", "replace") datere = re.compile(r"\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\.?\s+(\d{1,2})\b") rows = [] for tr in re.split(r"