diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-27 16:17:18 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-27 16:17:18 +0200 |
| commit | a1e041dfbe55194c7fea0636d38cff33ee28315d (patch) | |
| tree | 1138c5cbf5ca008dc3da11e93634c1017c63eeef /scripts | |
| parent | de102e446a715883c74e6295ff223e37e397f5ed (diff) | |
| download | lectio-a1e041dfbe55194c7fea0636d38cff33ee28315d.tar.gz lectio-a1e041dfbe55194c7fea0636d38cff33ee28315d.zip | |
feat(ef): complete offline EF readings — proper ferias, deuterocanon, Latin fallback
Finish the Extraordinary Form (1962) offline Mass readings so every day of
the year resolves in both English and Polish.
Temporal engine (temporal_ef.go):
- Unique ferial slugs "ef-<season>-<week>-<weekday>" so penitential-season
weekdays (Lent, Advent, Passiontide, Easter octave) can key their own
proper Masses; green-season ferias have no entry and fall back to the
preceding Sunday (the 1962 rule).
- Special "ef-lent-after-ashes-<weekday>" case for the days between Ash
Wednesday and Lent I, which have their own propers.
Lectionary (tridentine-lectionary.ini, 55 -> 143 entries) via genlect.go:
- Generator now fetches proper-season weekdays and stores a feria only when
its Mass differs from the preceding Sunday's.
- cleanCite() normalises two missalemeum data glitches: a stray comma
between book and chapter ("4 Kings, 5:1" -> "4 Kings 5:1") and a
period-as-separator ("John 20. 19-31" -> "John 20:19-31"), both guarded so
legitimate multi-chapter citations are untouched.
Citations & corpora:
- books.ini: Douay abbreviations in the [en] dialect (Ex, Ezech, Jonas, and
3/4 Kings -> canonical 1/2 Kings), so the EF Lenten epistles resolve while
the display keeps modern sigla.
- drb.tsv: +205 rows backfilling the deuterocanonical Daniel 13-14 (Susanna,
Bel) and Esther 11-16 (Greek additions) from get.bible douayrheims, which
the base corpus omitted; scripts/gen-deutero.py documents the fetch.
Rendering (liturgy.go):
- readingLine() falls back to the Latin Vulgate (complete) when the
vernacular corpus lacks a passage, with a clear note. Covers the Polish
Wujek deuterocanon gap (no clean public-domain source exists) and any
future vernacular gap; faithful to the EF as a Latin rite.
Result: 730/730 days resolve for both EN (native) and PL (native + Latin
fallback for 3 deuterocanon days). Version 0.32.0 -> 0.33.0.
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/gen-deutero.py | 62 | ||||
| -rw-r--r-- | scripts/genlect.go | 58 |
2 files changed, 110 insertions, 10 deletions
diff --git a/scripts/gen-deutero.py b/scripts/gen-deutero.py new file mode 100644 index 0000000..ace721e --- /dev/null +++ b/scripts/gen-deutero.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# gen-deutero.py -- backfill the deuterocanonical Daniel and Esther additions +# that the base drb (Douay-Rheims) corpus omits: Daniel 13 (Susanna) & 14 (Bel +# and the Dragon), and the Greek additions to Esther (chapters 11-16). The base +# corpus used Hebrew-canon chapter counts (Daniel 1-12, Esther 1-10) even though +# the Douay-Rheims itself carries these chapters; the EF Lenten lectionary reads +# them (e.g. Susanna on Saturday of the 3rd week of Lent), so they are required. +# +# Source: get.bible v2 "douayrheims" (public domain). Latin (vul) already has +# them; the Polish Wujek source (biblia.info.pl) uses the truncated 12-chapter +# Daniel, so wuj cannot be filled from the existing pipeline (Latin fallback +# covers Polish). +# +# Usage (from repo root): +# python3 scripts/gen-deutero.py # print the rows (inspect) +# python3 scripts/gen-deutero.py --apply # append to drb.tsv if not present +# +# Row format matches the corpus: Book\tAbbrev\tBookNum\tChapter\tVerse\tText +import json, sys, urllib.request + +DRB = "internal/bible/corpora/drb.tsv" +# (canonical book name, abbrev, book-number, [chapters]) -- must match drb.tsv. +TARGETS = [ + ("Daniel", "Dan", 27, range(13, 15)), # 13 Susanna, 14 Bel & the Dragon + ("Esther", "Est", 17, range(11, 17)), # 11-16 Greek additions +] + +def get(url): + req = urllib.request.Request(url, headers={"User-Agent": "curl/8.0"}) + with urllib.request.urlopen(req, timeout=25) as r: + return json.load(r) + +def rows(): + out = [] + for name, abbr, nr, chapters in TARGETS: + for ch in chapters: + d = get(f"https://api.getbible.net/v2/douayrheims/{nr}/{ch}.json") + for v in d.get("verses", []): + text = " ".join(v["text"].split()) # collapse whitespace, no tabs/newlines + out.append(f"{name}\t{abbr}\t{nr}\t{ch}\t{v['verse']}\t{text}") + return out + +def main(): + apply = "--apply" in sys.argv[1:] + new = rows() + if not apply: + for r in new: + print(r) + print(f"# {len(new)} rows (not written; pass --apply to append)", file=sys.stderr) + return + existing = open(DRB, encoding="utf-8").read() + if "\nDaniel\tDan\t27\t13\t" in existing: + print("drb.tsv already has Daniel 13 -- refusing to duplicate", file=sys.stderr) + sys.exit(1) + with open(DRB, "a", encoding="utf-8") as f: + if not existing.endswith("\n"): + f.write("\n") + f.write("\n".join(new) + "\n") + print(f"appended {len(new)} rows to {DRB}", file=sys.stderr) + +if __name__ == "__main__": + main() diff --git a/scripts/genlect.go b/scripts/genlect.go index eec21b6..85ff1c2 100644 --- a/scripts/genlect.go +++ b/scripts/genlect.go @@ -3,7 +3,9 @@ // genlect generates the EF temporal Sunday lectionary from missalemeum // (Divinum Officium data), keyed by lectio's computed temporal-day slug. // One-time; requires network. Run from the repo root: -// go run scripts/genlect.go +// +// go run scripts/genlect.go +// // Writes internal/caldata/tridentine-lectionary.ini. package main @@ -23,6 +25,26 @@ import ( var citeRe = regexp.MustCompile(`\*([^*]+)\*`) +// bookCommaRe strips a stray comma between the book name and the first +// chapter ("4 Kings, 5:1-15" -> "4 Kings 5:1-15"), a missalemeum data glitch. +// The [^:] guard means it only fires before any chapter:verse, so legitimate +// multi-chapter commas ("Gen 1:1, 2:3") are left intact. +var bookCommaRe = regexp.MustCompile(`^([^:]*?),\s+(\d+:)`) + +// chapDotRe rewrites a European-style "chapter. verse" separator to a colon +// ("John 20. 19-31" -> "John 20:19-31"), another missalemeum data glitch. It is +// applied only when the citation carries no colon at all, so disjoint verse +// groups in a normal citation ("Ps 62:2. 3-4") are never touched. +var chapDotRe = regexp.MustCompile(`(\d+)\.\s+(\d)`) + +func cleanCite(s string) string { + s = bookCommaRe.ReplaceAllString(strings.TrimSpace(s), "$1 $2") + if !strings.Contains(s, ":") { + s = chapDotRe.ReplaceAllString(s, "$1:$2") + } + return s +} + func fetchCitations(date string) (epistle, gospel string) { resp, err := http.Get("https://www.missalemeum.com/en/api/v5/proper/" + date) if err != nil { @@ -48,9 +70,9 @@ func fetchCitations(date string) (epistle, gospel string) { } switch s.ID { case "Lectio": - epistle = strings.TrimSpace(m[1]) + epistle = cleanCite(m[1]) case "Evangelium": - gospel = strings.TrimSpace(m[1]) + gospel = cleanCite(m[1]) } } return @@ -64,24 +86,40 @@ func main() { start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) end := time.Date(2028, 12, 31, 0, 0, 0, 0, time.UTC) + seen := map[string]bool{} + // Seasons whose weekdays may have PROPER Masses (else a ferial repeats the + // preceding Sunday and is resolved by the CLI fallback, so we skip it). + properFerial := map[calendar.Season]bool{ + calendar.Advent: true, calendar.Lent: true, + calendar.Passiontide: true, calendar.Easter_: true, + } for d := start; !d.After(end); d = d.AddDate(0, 0, 1) { - if d.Weekday() != time.Sunday { - continue - } day := calendar.Compute(d, sel, layers) - if day.Observed.Layer != "temporal" { // a feast won that Sunday -> its own propers + if day.Observed.Layer != "temporal" { // a feast won -> its own propers continue } slug := day.Observed.Slug - if _, ok := lect[slug]; ok { - continue // one instance per slug + isSunday := d.Weekday() == time.Sunday + if !isSunday && !properFerial[day.Season] { + continue // green-season feria -> repeats the Sunday + } + if seen[slug] { + continue } ep, gos := fetchCitations(d.Format("2006-01-02")) if ep == "" && gos == "" { continue } + seen[slug] = true + if !isSunday { + // store only if the reading differs from the preceding Sunday's Mass + sun := d.AddDate(0, 0, -int(d.Weekday())) + if r, ok := lect[calendar.Compute(sun, sel, layers).Observed.Slug]; ok && r[0] == ep && r[1] == gos { + continue // a repeat -> the CLI fallback handles it + } + } lect[slug] = [2]string{ep, gos} - fmt.Fprintf(os.Stderr, "%s %-34s ep=%-22s go=%s\n", d.Format("2006-01-02"), slug, ep, gos) + fmt.Fprintf(os.Stderr, "%s %-38s ep=%-22s go=%s\n", d.Format("2006-01-02"), slug, ep, gos) } slugs := make([]string, 0, len(lect)) |
