diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/caldata/caldata.go | 37 | ||||
| -rw-r--r-- | internal/caldata/stack_test.go | 16 | ||||
| -rw-r--r-- | internal/caldata/tridentine-calendar.ini | 112 | ||||
| -rw-r--r-- | internal/cli/liturgy.go | 30 | ||||
| -rw-r--r-- | internal/cli/liturgy_test.go | 15 |
5 files changed, 183 insertions, 27 deletions
diff --git a/internal/caldata/caldata.go b/internal/caldata/caldata.go index 9272fbb..14b3985 100644 --- a/internal/caldata/caldata.go +++ b/internal/caldata/caldata.go @@ -16,8 +16,11 @@ import ( //go:embed roman-calendar.ini var romanCalendar []byte -// Universal parses the embedded universal calendar. It panics on a malformed -// embedded file (a build-time bug caught by tests), never at the request layer. +//go:embed tridentine-calendar.ini +var tridentineCalendar []byte + +// Universal parses the embedded Ordinary Form universal calendar. It panics on a +// malformed embedded file (a build-time bug caught by tests). func Universal() calendar.Layer { l, err := ParseLayer(romanCalendar) if err != nil { @@ -26,6 +29,24 @@ func Universal() calendar.Layer { return l } +// Tridentine parses the embedded Extraordinary Form (1962) universal calendar. +func Tridentine() calendar.Layer { + l, err := ParseLayer(tridentineCalendar) + if err != nil { + panic(fmt.Sprintf("caldata: embedded tridentine-calendar.ini invalid: %v", err)) + } + return l +} + +// Base returns the universal base layer for a form: the 1962 calendar for +// "old" (EF), the General Roman Calendar otherwise (OF). +func Base(form string) calendar.Layer { + if form == "old" { + return Tridentine() + } + return Universal() +} + // LoadLayer reads and parses a user calendar layer file. id (the filename stem) // is used as the layer's ID when the file's [layer] header omits one. func LoadLayer(path, id string) (calendar.Layer, error) { @@ -43,12 +64,12 @@ func LoadLayer(path, id string) (calendar.Layer, error) { return l, nil } -// Stack returns the ordered layer stack for the engine: the universal base -// first, then each user layer named in use (matched to "<dir>/<id>.ini"), in -// order. A layer that fails to load is skipped and reported in the error slice -// so a single bad file never breaks calendar computation. -func Stack(dir string, use []string) ([]calendar.Layer, []error) { - layers := []calendar.Layer{Universal()} +// Stack returns the ordered layer stack for the engine: the form's universal +// base first, then each user layer named in use (matched to "<dir>/<id>.ini"), +// in order. A layer that fails to load is skipped and reported in the error +// slice so a single bad file never breaks calendar computation. +func Stack(form, dir string, use []string) ([]calendar.Layer, []error) { + layers := []calendar.Layer{Base(form)} var errs []error for _, id := range use { l, err := LoadLayer(filepath.Join(dir, id+".ini"), id) diff --git a/internal/caldata/stack_test.go b/internal/caldata/stack_test.go index b9d5bfb..3c1cf3f 100644 --- a/internal/caldata/stack_test.go +++ b/internal/caldata/stack_test.go @@ -13,7 +13,7 @@ func TestStack(t *testing.T) { os.WriteFile(filepath.Join(dir, "krakow.ini"), []byte("[layer]\nid = krakow\ntype = diocesan\n\n[st-stanislaus]\ndate = 05-08\nrank = solemnity\nclass = saint\ncolour = red\nname.pl = Św. Stanisława\n"), 0o644) - layers, errs := Stack(dir, []string{"poland", "krakow"}) + layers, errs := Stack("new", dir, []string{"poland", "krakow"}) if len(errs) != 0 { t.Fatalf("unexpected errors: %v", errs) } @@ -32,7 +32,7 @@ func TestStackMissingLayerSkips(t *testing.T) { dir := t.TempDir() os.WriteFile(filepath.Join(dir, "good.ini"), []byte("[layer]\nid = good\n\n[x]\ndate = 01-02\nrank = memorial\nname.en = X\n"), 0o644) - layers, errs := Stack(dir, []string{"good", "missing"}) + layers, errs := Stack("new", dir, []string{"good", "missing"}) if len(errs) != 1 { t.Fatalf("want 1 error for the missing layer, got %v", errs) } @@ -54,3 +54,15 @@ func TestLoadLayerDefaultsIDToStem(t *testing.T) { t.Errorf("ID = %q, want myparish (from stem)", l.ID) } } + +func TestStackFormBase(t *testing.T) { + dir := t.TempDir() + efLayers, _ := Stack("old", dir, nil) + if efLayers[0].ID != "tridentine" { + t.Errorf("EF base = %q want tridentine", efLayers[0].ID) + } + ofLayers, _ := Stack("new", dir, nil) + if ofLayers[0].ID != "universal" { + t.Errorf("OF base = %q want universal", ofLayers[0].ID) + } +} diff --git a/internal/caldata/tridentine-calendar.ini b/internal/caldata/tridentine-calendar.ini new file mode 100644 index 0000000..f3e9111 --- /dev/null +++ b/internal/caldata/tridentine-calendar.ini @@ -0,0 +1,112 @@ +; General Roman Calendar of 1962 (Extraordinary Form) — universal sanctoral. +; The temporal cycle (seasons, Sundays, Easter/Christmas/Epiphany, Ascension, +; Pentecost, Trinity, Corpus Christi, Sacred Heart, Christ the King) is computed +; by internal/calendar (temporalEF) and is NOT listed here. +; Ranks use the 1960 Code of Rubrics: class-1..class-4 + commemoration. +; INITIAL coverage; ranks/membership refined against the missalemeum oracle. +; Bootstrapped with reference to Divinum Officium / Missal1962 — see NOTICE. + +[layer] +id = tridentine +name = General Roman Calendar of 1962 +type = universal + +; --- I class --- + +[immaculate-conception] +date = 12-08 +rank = class-1 +colour = white +name.en = The Immaculate Conception of the Blessed Virgin Mary +name.la = In Conceptione Immaculata Beatae Mariae Virginis + +[st-joseph] +date = 03-19 +rank = class-1 +colour = white +name.en = Saint Joseph, Spouse of the Blessed Virgin Mary +name.la = Sancti Joseph Sponsi Beatae Mariae Virginis + +[annunciation] +date = 03-25 +rank = class-1 +colour = white +name.en = The Annunciation of the Blessed Virgin Mary +name.la = In Annuntiatione Beatae Mariae Virginis + +[nativity-john-baptist] +date = 06-24 +rank = class-1 +colour = white +name.en = The Nativity of Saint John the Baptist + +[peter-and-paul] +date = 06-29 +rank = class-1 +colour = red +name.en = Saints Peter and Paul, Apostles + +[assumption] +date = 08-15 +rank = class-1 +colour = white +name.en = The Assumption of the Blessed Virgin Mary +name.la = In Assumptione Beatae Mariae Virginis + +[st-michael] +date = 09-29 +rank = class-1 +colour = white +name.en = The Dedication of Saint Michael the Archangel + +[all-saints] +date = 11-01 +rank = class-1 +colour = white +name.en = All Saints + +; --- II class --- + +[purification] +date = 02-02 +rank = class-2 +colour = white +name.en = The Purification of the Blessed Virgin Mary + +[st-lawrence] +date = 08-10 +rank = class-2 +colour = red +name.en = Saint Lawrence, Martyr + +[nativity-bvm] +date = 09-08 +rank = class-2 +colour = white +name.en = The Nativity of the Blessed Virgin Mary + +[exaltation-of-the-cross] +date = 09-14 +rank = class-2 +colour = red +name.en = The Exaltation of the Holy Cross + +[st-andrew] +date = 11-30 +rank = class-2 +colour = red +name.en = Saint Andrew, Apostle + +; --- III class --- + +[conversion-of-paul] +date = 01-25 +rank = class-3 +colour = white +name.en = The Conversion of Saint Paul the Apostle + +[st-lucy] +date = 12-13 +rank = class-3 +colour = red +name.en = Saint Lucy, Virgin and Martyr diff --git a/internal/cli/liturgy.go b/internal/cli/liturgy.go index 24052ab..b1a8874 100644 --- a/internal/cli/liturgy.go +++ b/internal/cli/liturgy.go @@ -23,16 +23,8 @@ func runLiturgy(cfg config.Config, date string, stdout, stderr io.Writer) int { fmt.Fprintf(stderr, "lectio: bad date %q (want YYYY-MM-DD)\n", date) return 2 } - // The computed engine currently does the Ordinary Form only. Rather than - // silently return OF for a traditional (EF) config, say so. - if cfg.Selection().Form == "old" { - fmt.Fprintln(stderr, "lectio: the offline calendar engine currently computes only the Ordinary Form.") - fmt.Fprintln(stderr, " Traditional (1962 EF) computation is not built yet; your traditional daily") - fmt.Fprintln(stderr, " readings still work via 'lectio [DATE]'. To compute the OF: add --lectionary new.") - return 1 - } dir, _ := config.CalendarsDir() - layers, errs := caldata.Stack(dir, cfg.Use) + layers, errs := caldata.Stack(cfg.Selection().Form, dir, cfg.Use) for _, e := range errs { fmt.Fprintln(stderr, "lectio: warning:", e) } @@ -43,8 +35,12 @@ func runLiturgy(cfg config.Config, date string, stdout, stderr io.Writer) int { func printLiturgicalDay(out io.Writer, cfg config.Config, day calendar.LiturgicalDay) { fmt.Fprintf(out, "%s %s\n", day.Date.Format("2006-01-02"), day.Weekday.String()) - fmt.Fprintf(out, "season: %s (week %d · Sunday cycle %s · weekday cycle %s)\n", - day.Season, day.Week, day.SundayCycle, day.WeekdayCycle) + if day.SundayCycle != "" { // OF carries the reading cycles; EF does not + fmt.Fprintf(out, "season: %s (week %d · Sunday cycle %s · weekday cycle %s)\n", + day.Season, day.Week, day.SundayCycle, day.WeekdayCycle) + } else { + fmt.Fprintf(out, "season: %s (week %d)\n", day.Season, day.Week) + } fmt.Fprintf(out, "observed: %s [%s · %s]\n", celebrationName(cfg, day.Observed), rankLabel(day.Observed.Rank), day.Colour) for _, o := range day.Others { if n := celebrationName(cfg, o); n != "" { @@ -78,7 +74,7 @@ func celebrationName(cfg config.Config, c calendar.Celebration) string { if c.Slug == "" { return "(feria)" } - return strings.ReplaceAll(c.Slug, "-", " ") + return strings.ReplaceAll(strings.TrimPrefix(c.Slug, "ef-"), "-", " ") } func rankLabel(r calendar.Rank) string { @@ -91,6 +87,16 @@ func rankLabel(r calendar.Rank) string { return "memorial" case calendar.RankOptional: return "optional memorial" + case calendar.RankClass1: + return "I class" + case calendar.RankClass2: + return "II class" + case calendar.RankClass3: + return "III class" + case calendar.RankClass4: + return "IV class" + case calendar.RankCommemoration: + return "commemoration" default: return "feria" } diff --git a/internal/cli/liturgy_test.go b/internal/cli/liturgy_test.go index 02f02c4..c9d66a5 100644 --- a/internal/cli/liturgy_test.go +++ b/internal/cli/liturgy_test.go @@ -33,7 +33,7 @@ func TestRunLiturgy(t *testing.T) { } } -func TestRunLiturgyTraditionalGuarded(t *testing.T) { +func TestRunLiturgyTraditionalComputesEF(t *testing.T) { dir := t.TempDir() t.Setenv("LECTIO_CONFIG", filepath.Join(dir, "config.ini")) os.WriteFile(filepath.Join(dir, "config.ini"), []byte("lectionary = traditional\n"), 0o644) @@ -42,11 +42,16 @@ func TestRunLiturgyTraditionalGuarded(t *testing.T) { t.Fatal(err) } var buf bytes.Buffer - if code := runLiturgy(cfg, "2025-08-15", &buf, &buf); code == 0 { - t.Fatalf("traditional config should be guarded, not computed as OF:\n%s", buf.String()) + if code := runLiturgy(cfg, "2025-08-15", &buf, &buf); code != 0 { + t.Fatalf("EF compute failed (%d):\n%s", code, buf.String()) + } + out := buf.String() + // EF: Assumption is I class, and 2025-08-15 is in Time after Pentecost (not OF "ordinary"). + if !strings.Contains(out, "Assumption") || !strings.Contains(out, "I class") { + t.Errorf("expected EF Assumption (I class):\n%s", out) } - if !strings.Contains(strings.ToLower(buf.String()), "ordinary form") { - t.Errorf("expected an EF-not-implemented notice:\n%s", buf.String()) + if !strings.Contains(out, "time-after-pentecost") { + t.Errorf("expected EF season time-after-pentecost:\n%s", out) } } |
