summaryrefslogtreecommitdiff
path: root/cmd/clectio-gen
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/clectio-gen')
-rw-r--r--cmd/clectio-gen/main.go335
1 files changed, 335 insertions, 0 deletions
diff --git a/cmd/clectio-gen/main.go b/cmd/clectio-gen/main.go
new file mode 100644
index 0000000..167ed53
--- /dev/null
+++ b/cmd/clectio-gen/main.go
@@ -0,0 +1,335 @@
+// Command clectio-gen emits the compiled data for clectio, the tiny suckless-C
+// daily-readings build. It reuses lectio's validated calendar + lectionary
+// engine to compute, for every day in a year range, the day's name, colour, and
+// readings -- then writes two artifacts:
+//
+// liturgy_<form>.h : corpus-INDEPENDENT C tables (days, readings, citations,
+// and verse KEYS as indices) -- shipped, frozen.
+// verses_<form>.keys : the ordered (book,chapter,verse) keys, one per line --
+// shipped, resolved to text at clectio build time (mktext)
+// against whatever Vulgate-numbered corpus is compiled in.
+//
+// So the hard liturgical logic stays here (Go, oracle-validated); the C side is
+// a dumb lookup + renderer, and swapping the Bible never touches this generator.
+//
+// Usage: clectio-gen <new|old> <startYear> <endYear> <outdir>
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/bible"
+ "github.com/lukaszkasprzak/lectio/internal/config"
+ "github.com/lukaszkasprzak/lectio/internal/liturgy"
+ "github.com/lukaszkasprzak/lectio/internal/readings"
+)
+
+// colourIndex maps a liturgical colour to the small enum clectio prints.
+var colourIndex = map[string]int{
+ "green": 0, "white": 1, "red": 2, "violet": 3, "rose": 4, "black": 5,
+}
+
+// partLabel is the English section label clectio prints, by PartID.
+func partLabel(id string) string {
+ switch id {
+ case "pierwsze_czytanie":
+ return "First Reading"
+ case "psalm":
+ return "Responsorial Psalm"
+ case "drugie_czytanie":
+ return "Second Reading"
+ case "aklamacja":
+ return "Gospel Acclamation"
+ case "ewangelia", "evangelium":
+ return "Gospel"
+ case "epistola":
+ return "Epistle"
+ }
+ switch {
+ case strings.HasPrefix(id, "lectio"):
+ return "Lesson"
+ case strings.HasPrefix(id, "prophetia"):
+ return "Prophecy"
+ }
+ return "Reading"
+}
+
+// intern assigns a stable index to each distinct string, preserving first-seen
+// order.
+type intern struct {
+ idx map[string]int
+ list []string
+}
+
+func newIntern() *intern { return &intern{idx: map[string]int{}} }
+func (n *intern) get(s string) int {
+ if i, ok := n.idx[s]; ok {
+ return i
+ }
+ i := len(n.list)
+ n.idx[s] = i
+ n.list = append(n.list, s)
+ return i
+}
+
+// reading is one distinct pericope: its part label, citation in each sigla, and
+// the verse-key indices that make it up.
+type reading struct {
+ part, citeEN, citeLA int
+ verses []int
+}
+
+// day is one distinct liturgy: its name, colour, and ordered reading indices.
+type day struct {
+ name, colour int
+ readings []int
+}
+
+func main() {
+ if len(os.Args) != 5 {
+ fmt.Fprintln(os.Stderr, "usage: clectio-gen <new|old> <startYear> <endYear> <outdir>")
+ os.Exit(2)
+ }
+ form := os.Args[1] // "new" (OF) or "old" (EF)
+ lect := "new"
+ tag := "of"
+ if form == "old" {
+ lect, tag = "traditional", "ef"
+ }
+ y0, _ := strconv.Atoi(os.Args[2])
+ y1, _ := strconv.Atoi(os.Args[3])
+ outdir := os.Args[4]
+
+ base := func(sigla string) config.Config {
+ c := config.Default()
+ c.UILanguage = "en"
+ c.Lectionary = lect
+ c.SiglaStyle = sigla
+ return c
+ }
+ cfgEN, cfgLA := base("english"), base("latin")
+
+ names := newIntern()
+ parts := newIntern()
+ citesEN := newIntern()
+ citesLA := newIntern()
+ verseKeys := newIntern() // key "Book\tChap\tVerse" -> verse index
+ var readingList []reading
+ readingIdx := map[string]int{}
+ var dayList []day
+ dayIdx := map[string]int{}
+ var cal []int
+
+ internReading := func(secEN, secLA liturgy.Section) int {
+ keys := bible.LookupKeyed("vul", resolveRef(secEN, form))
+ if len(keys) == 0 {
+ return -1
+ }
+ vids := make([]int, len(keys))
+ for i, k := range keys {
+ vids[i] = verseKeys.get(fmt.Sprintf("%s\t%d\t%d", k.Book, k.Chapter, k.Verse))
+ }
+ r := reading{
+ part: parts.get(partLabel(secEN.PartID)),
+ citeEN: citesEN.get(secEN.Citation),
+ citeLA: citesLA.get(secLA.Citation),
+ verses: vids,
+ }
+ key := fmt.Sprintf("%d|%d|%d|%v", r.part, r.citeEN, r.citeLA, vids)
+ if i, ok := readingIdx[key]; ok {
+ return i
+ }
+ i := len(readingList)
+ readingIdx[key] = i
+ readingList = append(readingList, r)
+ return i
+ }
+
+ start := time.Date(y0, 1, 1, 0, 0, 0, 0, time.UTC)
+ end := time.Date(y1, 12, 31, 0, 0, 0, 0, time.UTC)
+ for d := start; !d.After(end); d = d.AddDate(0, 0, 1) {
+ ds := d.Format("2006-01-02")
+ secsEN, info, err := readings.Load(cfgEN, readings.Options{Date: ds, All: true})
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "clectio-gen: %s: %v\n", ds, err)
+ os.Exit(1)
+ }
+ secsLA, _, _ := readings.Load(cfgLA, readings.Options{Date: ds, All: true})
+
+ var rids []int
+ for i, secEN := range secsEN {
+ secLA := secEN
+ if i < len(secsLA) {
+ secLA = secsLA[i]
+ }
+ if id := internReading(secEN, secLA); id >= 0 {
+ rids = append(rids, id)
+ }
+ }
+ dy := day{
+ name: names.get(info.Name),
+ colour: colourIndex[info.Colour],
+ readings: rids,
+ }
+ key := fmt.Sprintf("%d|%d|%v", dy.name, dy.colour, rids)
+ id, ok := dayIdx[key]
+ if !ok {
+ id = len(dayList)
+ dayIdx[key] = id
+ dayList = append(dayList, dy)
+ }
+ cal = append(cal, id)
+ }
+
+ if err := os.MkdirAll(outdir, 0o755); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ writeKeys(filepath.Join(outdir, "verses_"+tag+".keys"), verseKeys.list)
+ writeHeader(filepath.Join(outdir, "liturgy_"+tag+".h"), tag, y0,
+ names.list, parts.list, citesEN.list, citesLA.list, readingList, dayList, cal)
+ fmt.Printf("clectio-gen %s: %d days, %d distinct days, %d readings, %d verses\n",
+ tag, len(cal), len(dayList), len(readingList), len(verseKeys.list))
+}
+
+// resolveRef mirrors render.resolveRef: prefer the English-canonical Ref, and
+// for the Ordinary Form renumber the Psalms to the Vulgate the keys come from.
+func resolveRef(sec liturgy.Section, form string) string {
+ cit := sec.Ref
+ if cit == "" {
+ cit = sec.Citation
+ }
+ if cit == "" {
+ if c, err := liturgy.ExtractCitation(sec.Heading); err == nil {
+ cit = c
+ }
+ }
+ if cit == "" {
+ return ""
+ }
+ if form != "new" {
+ return cit // EF citations are already Vulgate-numbered
+ }
+ return bible.OFRef(cit, "vulgate")
+}
+
+func writeKeys(path string, keys []string) {
+ f, err := os.Create(path)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ defer f.Close()
+ w := bufio.NewWriter(f)
+ for _, k := range keys {
+ w.WriteString(k + "\n")
+ }
+ w.Flush()
+}
+
+func cstr(s string) string {
+ r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, "\t", `\t`)
+ return `"` + r.Replace(s) + `"`
+}
+
+func writeHeader(path, tag string, y0 int, names, parts, citesEN, citesLA []string,
+ readings []reading, days []day, cal []int) {
+ f, err := os.Create(path)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ defer f.Close()
+ w := bufio.NewWriter(f)
+ defer w.Flush()
+
+ guard := "LITURGY_" + strings.ToUpper(tag) + "_H"
+ fmt.Fprintf(w, "/* generated by clectio-gen -- do not edit */\n#ifndef %s\n#define %s\n\n", guard, guard)
+ fmt.Fprintf(w, "#define EPOCH_Y %d\n#define EPOCH_M 1\n#define EPOCH_D 1\n#define NDAYS %d\n\n", y0, len(cal))
+
+ strArr := func(name string, xs []string) {
+ fmt.Fprintf(w, "static const char *const %s[] = {\n", name)
+ for _, s := range xs {
+ fmt.Fprintf(w, "\t%s,\n", cstr(s))
+ }
+ fmt.Fprintf(w, "};\n\n")
+ }
+ strArr("names", names)
+ strArr("parts", parts)
+ strArr("cites_en", citesEN)
+ strArr("cites_la", citesLA)
+
+ // Flat verse-index pool; each reading points at a [voff,voff+vlen) slice.
+ var vpool []int
+ fmt.Fprintf(w, "static const unsigned short vpool[] = {")
+ for _, r := range readings {
+ for _, v := range r.verses {
+ vpool = append(vpool, v)
+ }
+ }
+ for i, v := range vpool {
+ if i%16 == 0 {
+ fmt.Fprintf(w, "\n\t")
+ }
+ fmt.Fprintf(w, "%d,", v)
+ }
+ fmt.Fprintf(w, "\n};\n\n")
+
+ fmt.Fprintf(w, "typedef struct { unsigned short part, cite, voff, vlen; } Reading;\n")
+ fmt.Fprintf(w, "static const Reading readings[] = {\n")
+ voff := 0
+ for _, r := range readings {
+ fmt.Fprintf(w, "\t{%d,%d,%d,%d},\n", r.part, r.citeEN, voff, len(r.verses))
+ voff += len(r.verses)
+ }
+ fmt.Fprintf(w, "};\n\n")
+ // cite_la parallels readings[] by index (same part/verses, latin citation).
+ fmt.Fprintf(w, "static const unsigned short readings_cite_la[] = {")
+ for i, r := range readings {
+ if i%16 == 0 {
+ fmt.Fprintf(w, "\n\t")
+ }
+ fmt.Fprintf(w, "%d,", r.citeLA)
+ }
+ fmt.Fprintf(w, "\n};\n\n")
+
+ // Flat reading-index pool; each day points at a [roff,roff+rlen) slice.
+ var rpool []int
+ fmt.Fprintf(w, "static const unsigned short rpool[] = {")
+ for _, d := range days {
+ for _, r := range d.readings {
+ rpool = append(rpool, r)
+ }
+ }
+ for i, r := range rpool {
+ if i%16 == 0 {
+ fmt.Fprintf(w, "\n\t")
+ }
+ fmt.Fprintf(w, "%d,", r)
+ }
+ fmt.Fprintf(w, "\n};\n\n")
+
+ fmt.Fprintf(w, "typedef struct { unsigned short name; unsigned char colour; unsigned short roff, rlen; } Day;\n")
+ fmt.Fprintf(w, "static const Day days[] = {\n")
+ roff := 0
+ for _, d := range days {
+ fmt.Fprintf(w, "\t{%d,%d,%d,%d},\n", d.name, d.colour, roff, len(d.readings))
+ roff += len(d.readings)
+ }
+ fmt.Fprintf(w, "};\n\n")
+
+ fmt.Fprintf(w, "static const unsigned short cal[NDAYS] = {")
+ for i, c := range cal {
+ if i%16 == 0 {
+ fmt.Fprintf(w, "\n\t")
+ }
+ fmt.Fprintf(w, "%d,", c)
+ }
+ fmt.Fprintf(w, "\n};\n\n#endif\n")
+}