//go:build ignore // 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 // // Writes internal/caldata/tridentine-lectionary.ini. package main import ( "encoding/json" "fmt" "net/http" "os" "regexp" "sort" "strings" "time" "github.com/lukaszkasprzak/lectio/internal/caldata" "github.com/lukaszkasprzak/lectio/internal/calendar" ) 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 { return } defer resp.Body.Close() var data []struct { Sections []struct { ID string `json:"id"` Body [][]string `json:"body"` // [[english, latin]] } `json:"sections"` } if json.NewDecoder(resp.Body).Decode(&data) != nil || len(data) == 0 { return } for _, s := range data[0].Sections { if len(s.Body) == 0 || len(s.Body[0]) == 0 { continue } m := citeRe.FindStringSubmatch(s.Body[0][0]) // english text carries *citation* if m == nil { continue } switch s.ID { case "Lectio": epistle = cleanCite(m[1]) case "Evangelium": gospel = cleanCite(m[1]) } } return } func main() { sel := calendar.DefaultSelection() sel.Form = "old" layers := []calendar.Layer{caldata.Tridentine()} lect := map[string][2]string{} // slug -> {epistle, gospel} 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) { day := calendar.Compute(d, sel, layers) if day.Observed.Layer != "temporal" { // a feast won -> its own propers continue } slug := day.Observed.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 %-38s ep=%-22s go=%s\n", d.Format("2006-01-02"), slug, ep, gos) } slugs := make([]string, 0, len(lect)) for s := range lect { slugs = append(slugs, s) } sort.Strings(slugs) var b strings.Builder b.WriteString("; EF (1962) temporal lectionary, keyed by lectio's computed temporal-day slug.\n") b.WriteString("; Generated from missalemeum (Divinum Officium). Epistle (first) + Gospel citations.\n") for _, s := range slugs { v := lect[s] fmt.Fprintf(&b, "\n[%s]\n", s) if v[0] != "" { fmt.Fprintf(&b, "first = %s\n", v[0]) } if v[1] != "" { fmt.Fprintf(&b, "gospel = %s\n", v[1]) } } if err := os.WriteFile("internal/caldata/tridentine-lectionary.ini", []byte(b.String()), 0o644); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Fprintf(os.Stderr, "wrote %d temporal entries\n", len(lect)) }