//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 -. One-time; requires network. Run // from the repo root: // // go run scripts/genlect-of.go // // Writes internal/caldata/of-lectionary.ini. package main import ( "fmt" "os" "regexp" "sort" "strings" "time" "github.com/lukaszkasprzak/lectio/internal/bible" "github.com/lukaszkasprzak/lectio/internal/caldata" "github.com/lukaszkasprzak/lectio/internal/calendar" "github.com/lukaszkasprzak/lectio/internal/config" "github.com/lukaszkasprzak/lectio/internal/readings" ) // bookCommaRe strips a stray comma between the book name and the first // chapter ("4 Kings, 5:1-15" -> "4 Kings 5:1-15"), a source 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. // Copied verbatim from scripts/genlect.go. 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 source 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. // Copied verbatim from scripts/genlect.go. 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 } // 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. var partIDToPart = map[string]string{ "pierwsze_czytanie": "first", "psalm": "psalm", "drugie_czytanie": "second", "ewangelia": "gospel", } func main() { sel := calendar.DefaultSelection() sel.Form = "new" layers := []calendar.Layer{caldata.Universal()} cfg := config.Default() cfg.Lectionary = "new" lect := map[string]map[string]string{} // key -> part -> citation seen := map[string]bool{} var missing []string // date+key kept but missing first/gospel // 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) 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 } key := day.Observed.Slug + "-" + day.SundayCycle if seen[key] { continue } 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)) continue } parts := map[string]string{} for _, s := range secs { part, ok := partIDToPart[s.PartID] if !ok { continue } system := "" if part == "psalm" { system = "drb" } eng, err := bible.ToEnglishRef(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) continue } parts[part] = cleanCite(eng) } if parts["first"] == "" || parts["gospel"] == "" { missing = append(missing, fmt.Sprintf("%s %s (first=%q gospel=%q)", dateStr, key, parts["first"], parts["gospel"])) continue // require at least first+gospel to store the entry } lect[key] = parts 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"]) } keys := make([]string, 0, len(lect)) for k := range lect { keys = append(keys, k) } sort.Strings(keys) var b strings.Builder b.WriteString("; OF (Ordinary Form) Sunday & solemnity lectionary, keyed by\n") b.WriteString("; -. Citations English-canonical.\n") b.WriteString("; Generated from niedziela.pl by scripts/genlect-of.go (2025-2027).\n") for _, k := range keys { v := lect[k] fmt.Fprintf(&b, "\n[%s]\n", k) for _, part := range []string{"first", "psalm", "second", "gospel"} { if c := v[part]; c != "" { fmt.Fprintf(&b, "%s = %s\n", part, c) } } } if err := os.WriteFile("internal/caldata/of-lectionary.ini", []byte(b.String()), 0o644); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } 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) } } }