diff options
Diffstat (limited to 'cmd/clectio-gen')
| -rw-r--r-- | cmd/clectio-gen/main.go | 335 |
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") +} |
