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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
#!/usr/bin/env python3
"""Regenerate the Ordinary Form WEEKDAY (ferial) lectionary for Ordinary Time
from catholic-resources.org (Fr. Felix Just, S.J.), replacing the entries that
were harvested from niedziela.pl.
Why: the niedziela harvest is by-date, so it (a) stores a saint's readings under
a ferial key whenever a saint displaced the ferial on the queried date, and
(b) inherits lectio's old Ordinary-Time week-number off-by-one. catholic-
resources.org is keyed by liturgical POSITION (week + weekday + year), immune to
both, complete (all 34 weeks x both years), and gives citations only (no text),
which matches lectio's licensing. Scripture citations only; see NOTICE.
Requires network. From the repo root: python3 scripts/genlect-of-cr.py
Rewrites the ordinary-<week>-<day>-<I|II> entries in
internal/caldata/of-lectionary.ini, keyed by the canonical week number (the same
number temporalEF/temporal now computes).
"""
import re, sys, urllib.request
INI = "internal/caldata/of-lectionary.ini"
SRC = {"I": "https://www.catholic-resources.org/Lectionary/2002USL-Weekdays-OT-I.htm",
"II": "https://www.catholic-resources.org/Lectionary/2002USL-Weekdays-OT-II.htm"}
DAYMAP = {"mon": "mon", "tues": "tue", "tue": "tue", "wed": "wed",
"thurs": "thu", "thu": "thu", "fri": "fri", "sat": "sat"}
# CR abbreviation -> lectio full book name (matches the existing file's style).
BOOK = {}
def reg(full, *aliases):
for a in aliases:
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");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");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")
def norm_cite(c):
c = re.sub(r"^\s*See\s+", "", c.strip())
c = re.sub(r"[–—]|—|–", "-", c) # en/em dash -> hyphen
c = re.sub(r"\s+", " ", c).strip()
m = re.match(r"((?:[1-4]\s+)?[A-Za-z][A-Za-z]*\.?)\s*(.*)", c)
if not m:
return c
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 {c!r}\n")
full = m.group(1)
verses = re.sub(r",\s+", ",", m.group(2)).replace(" ", "") # match file style: no spaces
return f"{full} {verses}".strip()
def strip_tags(s):
return re.sub(r"<[^>]+>", "", s).replace(" ", " ").strip()
def scrape(url, year):
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (lectio OF lectionary generator)"})
html = urllib.request.urlopen(req, timeout=40).read().decode("utf-8", "replace")
out = {}
for tr in re.split(r"<tr\b", html):
cells = [strip_tags(c) for c in re.findall(r"<td\b[^>]*>(.*?)</td>", tr, re.S)]
if len(cells) < 6:
continue
m = re.match(r"Week (\d+)\s*-\s*(Mon|Tues|Tue|Wed|Thurs|Thu|Fri|Sat)\b", cells[1])
if not m:
continue
wk, day = int(m.group(1)), DAYMAP[m.group(2).lower()]
out[(wk, day, year)] = {"first": norm_cite(cells[2]),
"psalm": norm_cite(cells[3]),
"gospel": norm_cite(cells[5])}
return out
def main():
cr = {}
for yr, url in SRC.items():
d = scrape(url, yr)
sys.stderr.write(f"{url}: {len(d)} rows\n")
cr.update(d)
# parse existing file, keep header comment + all non-OT-ferial entries verbatim
text = open(INI).read()
head = text[:text.index("\n[")]
entries = {}
for block in re.split(r"\n(?=\[)", text[text.index("\n[") + 1:]):
m = re.match(r"\[(.+?)\]", block)
if m:
entries[m.group(1)] = block.rstrip("\n")
ot = re.compile(r"^ordinary-\d+-(mon|tue|wed|thu|fri|sat)-(I|II)$")
kept = {k: v for k, v in entries.items() if not ot.match(k)}
for (wk, day, yr), parts in cr.items():
key = f"ordinary-{wk}-{day}-{yr}"
lines = [f"[{key}]"]
for p in ("first", "psalm", "gospel"):
if parts[p]:
lines.append(f"{p} = {parts[p]}")
kept[key] = "\n".join(lines)
out = head.rstrip("\n") + "\n"
for k in sorted(kept):
out += "\n" + kept[k] + "\n"
open(INI, "w").write(out)
n = sum(1 for k in kept if k.startswith("ordinary-") and ot.match(k))
sys.stderr.write(f"wrote {len(kept)} entries ({n} OT ferials from catholic-resources.org)\n")
if __name__ == "__main__":
main()
|