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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
//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:
//
// 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("; <computed-temporal-slug>-<SundayCycle>. 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)
}
}
}
|