aboutsummaryrefslogtreecommitdiff
path: root/scripts/genlect-of.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 00:47:16 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-28 00:47:16 +0200
commite829b48b08c683d9dcef5e4b9502c1c42bc895f1 (patch)
tree63afd5857a2a98e476a6d7e8fc10b25056b905eb /scripts/genlect-of.go
parent6c320f88cc5659233001894614e39265ca4e6363 (diff)
downloadlectio-e829b48b08c683d9dcef5e4b9502c1c42bc895f1.tar.gz
lectio-e829b48b08c683d9dcef5e4b9502c1c42bc895f1.zip
feat(of): full sanctoral calendar + weekday/memorial readings
Populate the OF General Roman Calendar sanctoral and extend the OF readings to weekdays and sanctoral days. Sanctoral calendar (roman-calendar.ini: 29 -> 202 celebrations): - scripts/gen-sanctoral.go generates it from calapi.inadiutorium.cz (authoritative General Roman Calendar) -- English name/rank/colour from general-en, Latin from general-la, Polish from niedziela DayInfo (obligatory celebrations). Fixed dates the temporal engine already computes are excluded. VALIDATED: my engine's observed rank agrees with calapi on all 86 obligatory sanctoral days of 2026 (0 mismatches). Precedence fix (precedence.go): an optional memorial no longer displaces the weekday as the DEFAULT observed celebration (it sorts below the ferial, shown as an option) -- matching the General Roman Calendar / calapi. Readings (of-lectionary.ini: 254 -> 1018 entries) via genlect-of.go: - Weekday (ferial) 2-year cycle I/II, keyed <ferial-slug>-<WeekdayCycle>, harvested from niedziela over 2020-2025 (multiple years per cycle; retry-on-failure so a glitch year never poisons a key; only TRUE ferials + optional-memorial days, where niedziela shows the ferial). - Obligatory memorials keyed by their OWN slug (niedziela shows their proper, e.g. Barnabas -> Acts 11, or the ferial for memorials without a proper) -- never miskeyed to a ferial position. - caldata.Readings: OF resolves slug+SundayCycle | slug+WeekdayCycle, else a memorial falls back to the day's ferial readings. - Source-glitch normalisation (1J->1 J, PnP->Pnp). books.ini: canonical names "Song of Solomon" and "The Acts" added as their own [en] forms (ToEnglishRef emits the canonical; it must resolve). Fixes ~32 cross-chapter Song-of-Songs/Acts citations. Coverage: 359/365 days of 2026 render (98.4%); 6 residual ferial positions never a true-ferial in 2020-2025. EF sanctoral and OF psalm renumbering still pending.
Diffstat (limited to 'scripts/genlect-of.go')
-rw-r--r--scripts/genlect-of.go103
1 files changed, 74 insertions, 29 deletions
diff --git a/scripts/genlect-of.go b/scripts/genlect-of.go
index 6126dea..00d0992 100644
--- a/scripts/genlect-of.go
+++ b/scripts/genlect-of.go
@@ -1,9 +1,11 @@
//go:build ignore
-// genlect-of generates the OF (Ordinary Form) Sunday/solemnity temporal
-// lectionary from niedziela.pl (via internal/readings), keyed by lectio's
-// computed <Observed.Slug>-<SundayCycle>. One-time; requires network. Run
-// from the repo root:
+// genlect-of generates the OF (Ordinary Form) temporal lectionary from
+// niedziela.pl (via internal/readings): Sundays & solemnities keyed by
+// <Observed.Slug>-<SundayCycle> (A/B/C), ferial weekdays by <ferial-slug>-
+// <WeekdayCycle> (I/II). Only TRUE ferials are harvested for the weekday table;
+// memorial days are skipped (their readings come from the ferial fallback at
+// runtime). One-time; requires network. Run from the repo root:
//
// go run scripts/genlect-of.go
//
@@ -47,6 +49,27 @@ func cleanCite(s string) string {
return s
}
+// numBookSpaceRe restores the space niedziela sometimes drops in a numbered
+// book sigil ("1J 3,11" -> "1 J 3,11"; "2Kor" -> "2 Kor") so ToEnglishRef's
+// Polish table (keyed "1 J", "2 Kor", …) matches. Applied to the raw Polish
+// citation BEFORE ToEnglishRef.
+var numBookSpaceRe = regexp.MustCompile(`^(\d)([^\d\s])`)
+
+// sourceGlitches are exact niedziela sigla-casing glitches that don't match the
+// ToEnglishRef Polish table (which is Title-case). E.g. "PnP" for Song of Songs.
+var sourceGlitches = map[string]string{"PnP": "Pnp"}
+
+// fixSourceCite normalises a raw niedziela citation before ToEnglishRef.
+func fixSourceCite(s string) string {
+ s = numBookSpaceRe.ReplaceAllString(strings.TrimSpace(s), "$1 $2")
+ for bad, good := range sourceGlitches {
+ if strings.HasPrefix(s, bad+" ") {
+ s = good + s[len(bad):]
+ }
+ }
+ return s
+}
+
// partIDToPart maps a niedziela.pl (liturgy.Section) PartID to our lectionary
// field name. Anything absent (e.g. "aklamacja") is not a reading and is
// ignored.
@@ -67,35 +90,49 @@ func main() {
lect := map[string]map[string]string{} // key -> part -> citation
seen := map[string]bool{}
- var missing []string // date+key kept but missing first/gospel
+ missing := map[string]string{} // key -> last failure reason; cleared on success
- // Harvest the PAST cycle-equivalent years: 2023=A, 2024=B, 2025=C. niedziela
- // only publishes ~6 weeks ahead, so future years (2026-2027) are unavailable;
- // the 3-year cycle repeats, so a liturgical position's cycle-A readings are the
- // same whether they fall in 2023 or 2026. All three years are past and archived.
- start := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
+ // niedziela only publishes ~6 weeks ahead, so future years are unavailable;
+ // the cycles repeat, so a liturgical position's readings are identical
+ // whichever civil year they fall in. Harvest 2020-2025 -- TWO+ years per
+ // cycle (Sunday A:2020,2023 B:2021,2024 C:2022,2025; weekday I:2021,2023,2025
+ // II:2020,2022,2024) so a position displaced by a saint/feast in one year is
+ // still captured in another. A key is stored from the FIRST year it resolves;
+ // a year where it fails does not block a later retry (seen is set on success).
+ start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC)
for d := start; !d.After(end); d = d.AddDate(0, 0, 1) {
- day := calendar.Compute(d, sel, layers)
- keep := d.Weekday() == time.Sunday ||
- day.Observed.Rank == calendar.RankSolemnity ||
- day.Observed.Rank == calendar.RankFeast
- if !keep {
- continue
- }
+ full := calendar.Compute(d, sel, layers)
+ sunOrSol := d.Weekday() == time.Sunday ||
+ full.Observed.Rank == calendar.RankSolemnity ||
+ full.Observed.Rank == calendar.RankFeast
- key := day.Observed.Slug + "-" + day.SundayCycle
+ var key string
+ if sunOrSol {
+ key = full.Observed.Slug + "-" + full.SundayCycle
+ } else if full.Observed.Rank == calendar.RankFerial && full.Observed.Layer == "temporal" {
+ // A true ferial (incl. optional-memorial days, where the ferial is the
+ // observed default): niedziela shows the ferial readings.
+ key = full.Observed.Slug + "-" + full.WeekdayCycle
+ } else if full.Observed.Rank == calendar.RankMemorial {
+ // An obligatory memorial: niedziela shows its PROPER readings (e.g. St
+ // Barnabas, Acts 11) or, for memorials without proper readings, the
+ // ferial. Either way it is keyed by the memorial's OWN slug, so it is
+ // correctly attributed and never miskeyed to a ferial position.
+ key = full.Observed.Slug + "-" + full.WeekdayCycle
+ } else {
+ continue // (unreachable: optional memorials are ferial-observed)
+ }
if seen[key] {
- continue
+ continue // already stored from an earlier year
}
- seen[key] = true
dateStr := d.Format("2006-01-02")
secs, _, err := readings.Load(cfg, readings.Options{Date: dateStr, All: true})
if err != nil {
fmt.Fprintf(os.Stderr, "%s %-32s ERROR readings.Load: %v\n", dateStr, key, err)
- missing = append(missing, fmt.Sprintf("%s %s (readings.Load error: %v)", dateStr, key, err))
+ missing[key] = fmt.Sprintf("%s (readings.Load error: %v)", dateStr, err)
continue
}
@@ -109,7 +146,7 @@ func main() {
if part == "psalm" {
system = "drb"
}
- eng, err := bible.ToEnglishRef(s.Citation, system)
+ eng, err := bible.ToEnglishRef(fixSourceCite(s.Citation), system)
if err != nil {
fmt.Fprintf(os.Stderr, "%s %-32s part=%-6s citation=%q: ToEnglishRef error: %v\n",
dateStr, key, part, s.Citation, err)
@@ -119,11 +156,13 @@ func main() {
}
if parts["first"] == "" || parts["gospel"] == "" {
- missing = append(missing, fmt.Sprintf("%s %s (first=%q gospel=%q)", dateStr, key, parts["first"], parts["gospel"]))
+ missing[key] = fmt.Sprintf("%s (first=%q gospel=%q)", dateStr, parts["first"], parts["gospel"])
continue // require at least first+gospel to store the entry
}
lect[key] = parts
+ seen[key] = true // store from the first year it resolves; stop retrying
+ delete(missing, key) // a later year succeeded; not actually missing
fmt.Fprintf(os.Stderr, "%s %-32s first=%-22s psalm=%-22s second=%-22s gospel=%s\n",
dateStr, key, parts["first"], parts["psalm"], parts["second"], parts["gospel"])
}
@@ -135,9 +174,10 @@ func main() {
sort.Strings(keys)
var b strings.Builder
- b.WriteString("; OF (Ordinary Form) Sunday & solemnity lectionary, keyed by\n")
- b.WriteString("; <computed-temporal-slug>-<SundayCycle>. Citations English-canonical.\n")
- b.WriteString("; Generated from niedziela.pl by scripts/genlect-of.go (2025-2027).\n")
+ b.WriteString("; OF (Ordinary Form) temporal lectionary, keyed by <computed-temporal-slug>-<cycle>:\n")
+ b.WriteString("; Sundays & solemnities use the Sunday cycle (A/B/C), ferial weekdays the weekday\n")
+ b.WriteString("; cycle (I/II). Citations English-canonical.\n")
+ b.WriteString("; Generated from niedziela.pl by scripts/genlect-of.go (harvest 2020-2025).\n")
for _, k := range keys {
v := lect[k]
fmt.Fprintf(&b, "\n[%s]\n", k)
@@ -154,9 +194,14 @@ func main() {
fmt.Fprintf(os.Stderr, "\nwrote %d OF temporal entries\n", len(lect))
if len(missing) > 0 {
- fmt.Fprintf(os.Stderr, "%d kept days missing first/gospel (not stored):\n", len(missing))
- for _, m := range missing {
- fmt.Fprintln(os.Stderr, m)
+ mk := make([]string, 0, len(missing))
+ for k := range missing {
+ mk = append(mk, k)
+ }
+ sort.Strings(mk)
+ fmt.Fprintf(os.Stderr, "%d keys NEVER resolved in any harvest year:\n", len(missing))
+ for _, k := range mk {
+ fmt.Fprintf(os.Stderr, " %-34s %s\n", k, missing[k])
}
}
}