// Command lectio-ef-dump prints lectio's Extraordinary Form (1962) calendar, // one line per day, for use as colitur's differential oracle. // // Output format is colitur's `colitur day ` line, PLUS four columns // colitur's own line does not carry (so a straight `diff` against colitur's // output must ignore trailing fields, not compare them line for line): // // YYYY-MM-DD weekday season week slug rank colour name_en name_pl first gospel [+other-slug]... // // name_en and name_pl are the observed celebration's own display name in // English and Polish (Celebration.Name["en"]/["pl"]), spaces replaced with // "_" so the line stays whitespace-delimited; "-" where the name is empty // (a bare feria with no proper name). Added after a regeneration of // internal/caldata/tridentine-calendar.ini once deleted all 322 Polish // names (name.pl 322 -> 0) with nothing in this repository's test suite OR // this dumper noticing: the season/rank/colour columns this tool already // printed were all still correct, since name is a wholly separate field // naming.CelebrationName reads independently. This dumper is what the // task's own before/after diff verification is run against, so it needed // to be structurally capable of seeing a name regression, not just told to // look harder next time. // // first and gospel are the Epistle/Lesson and Gospel citations RESOLVED for // the day (Reading.Part "first"/"gospel"), not a raw lectionary-table lookup // keyed by slug: they come from caldata.Readings, the same function the CLI // and calfeed use, which applies the day's own inline propers first and // falls back to the 1962 rule that a green-season weekday with no proper // Mass repeats the PRECEDING SUNDAY's Mass (via calendar.TemporalSlug, which // still finds that Sunday's temporal identity even when a feast displaced it // as the observed celebration). That fallback is exactly why this column is // worth comparing: colitur implements the same rubric independently, so this // checks two implementations against each other rather than one against // silence. Spaces become "_" (dumpField, shared with name_en/name_pl); "-" // where the day has no such reading (e.g. no epistle/gospel citation at // all, or the Mass proper does not carry that part). // // Every column carries lectio's OWN vocabulary (season names, slugs, rank and // colour spellings) -- this dumper does not translate lectio's values into // colitur's. The mapping between the two vocabularies is the differential // comparator's job, not this dumper's; translating here would hide real // divergences behind an already-reconciled view. // // The trailing "+other-slug" tokens are lectio's Others: the sanctoral // candidates that lost the day's precedence contest. lectio has no RG 111 // commemoration-admission logic, so this is the set of losing candidates, not // the set of admitted commemorations -- the two are different things. See the // task report for detail; the comparator does not diff this column. // // Usage: lectio-ef-dump package main import ( "bufio" "fmt" "io" "os" "strconv" "strings" "time" "github.com/lukaszkasprzak/lectio/internal/caldata" "github.com/lukaszkasprzak/lectio/internal/calendar" ) func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } // run is lectio-ef-dump's testable entry point: parse args, compute, and // write. Returns a process exit code (0 ok, 1 runtime error, 2 usage error). func run(args []string, stdout, stderr io.Writer) int { if len(args) != 2 { fmt.Fprintln(stderr, "usage: lectio-ef-dump ") return 2 } from, errFrom := strconv.Atoi(args[0]) to, errTo := strconv.Atoi(args[1]) if errFrom != nil || errTo != nil { fmt.Fprintln(stderr, "lectio-ef-dump: from-year and to-year must be integers") return 2 } if from > to { fmt.Fprintf(stderr, "lectio-ef-dump: from-year %d is after to-year %d\n", from, to) return 2 } sel := calendar.DefaultSelection() sel.Form = "old" layers := []calendar.Layer{caldata.Tridentine()} w := bufio.NewWriter(stdout) for y := from; y <= to; y++ { start := time.Date(y, time.January, 1, 0, 0, 0, 0, time.UTC) end := time.Date(y, time.December, 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) rs := caldata.Readings(sel, layers, d, day) if _, err := w.WriteString(dumpLine(day, rs)); err != nil { fmt.Fprintf(stderr, "lectio-ef-dump: %v\n", err) return 1 } } } if err := w.Flush(); err != nil { fmt.Fprintf(stderr, "lectio-ef-dump: %v\n", err) return 1 } return 0 } // dumpField renders a whitespace-delimited field: spaces become "_" (values // routinely contain spaces, e.g. the name "St. Thomas Becket" or the citation // "Rom 13:11-14"); "-" for an empty value, matching the week column's own // convention for "absent". Shared by the name_en/name_pl columns and the // first/gospel reading-citation columns. func dumpField(s string) string { if s == "" { return "-" } return strings.ReplaceAll(s, " ", "_") } // citationFor returns the citation of the first reading of the given Part // ("first" or "gospel") among rs, or "" if rs carries none. func citationFor(rs []calendar.Reading, part string) string { for _, r := range rs { if r.Part == part { return r.Citation } } return "" } // dumpLine renders one LiturgicalDay as a colitur-format line, plus the // name_en/name_pl/first/gospel columns colitur's own line does not carry // (see this package's doc comment). rs is the day's RESOLVED readings // (caldata.Readings' result), not day.Observed.Masses directly: for a // green-season weekday that repeats the preceding Sunday's Mass, // day.Observed carries the temporal celebration's own (proper-less) Masses, // and only caldata.Readings applies that fallback. Week is lectio's own int // (0 where no season week applies, e.g. named I class feasts and per annum // green-season ferias); printed as "-" there so the field count stays fixed, // matching colitur's own convention for an absent week. func dumpLine(day calendar.LiturgicalDay, rs []calendar.Reading) string { week := "-" if day.Week != 0 { week = strconv.Itoa(day.Week) } var b strings.Builder fmt.Fprintf(&b, "%s %s %s %s %s %s %s %s %s %s %s", day.Date.Format("2006-01-02"), strings.ToLower(day.Weekday.String()), string(day.Season), week, day.Observed.Slug, string(day.Observed.Rank), string(day.Colour), dumpField(day.Observed.Name["en"]), dumpField(day.Observed.Name["pl"]), dumpField(citationFor(rs, "first")), dumpField(citationFor(rs, "gospel")), ) for _, o := range day.Others { fmt.Fprintf(&b, " +%s", o.Slug) } b.WriteByte('\n') return b.String() }