aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/caldata/caldata_test.go54
-rw-r--r--internal/caldata/tridentine-calendar.ini16
-rw-r--r--scripts/gen-sanctoral-ef.go107
3 files changed, 166 insertions, 11 deletions
diff --git a/internal/caldata/caldata_test.go b/internal/caldata/caldata_test.go
index 7dbbf84..c0e2d50 100644
--- a/internal/caldata/caldata_test.go
+++ b/internal/caldata/caldata_test.go
@@ -402,3 +402,57 @@ func TestTridentineNamesPreservedAcrossRegeneration(t *testing.T) {
t.Errorf("assumption-of-the-blessed-virgin-mary: name.pl = %q, want the preserved Polish name", got)
}
}
+
+// TestTridentineMissalOverrides pins the four fields where the 1962 MISSALE
+// ROMANUM decides against missalemeum, and the Missal wins.
+//
+// This is the safety net for a specific accident. This file is generated by
+// scripts/gen-sanctoral-ef.go from missalemeum, which does NOT carry any of
+// these values; the generator re-applies them from its own missalOverrides
+// table on every run. If that table is ever removed, or a slug is renamed
+// upstream so an override silently stops matching, the generated file quietly
+// reverts to missalemeum's values and nothing else in this repository would
+// notice. This test notices.
+//
+// Each was found by the sibling project colitur, which validates this data
+// against the Missal and its Rubricae Generales rather than against another
+// calendar program:
+//
+// - ubaldus and didacus: the Missal's own universal calendarium ranks both
+// "III classis" outright, with no commemoration rubric, and each has a
+// Mass entry of its own in the Proprium taking its readings from a Common
+// -- which a bare commemoration never has.
+// - the two August vigils: RG 128 gives violet to "vigils of II and III
+// class outside Paschaltide". Both qualify. The rule's sole exception is
+// the Ascension's vigil, inside Paschaltide, which is white -- and which
+// is therefore checked here too, in the opposite direction, so that a
+// careless "make every vigil violet" fix would fail rather than pass.
+func TestTridentineMissalOverrides(t *testing.T) {
+ l := Tridentine()
+ for _, c := range []struct{ slug, field, want, why string }{
+ {"ubaldus", "rank", "class-3", "Missal calendarium: S. Ubaldi Ep. et Conf., III classis"},
+ {"didacus", "rank", "class-3", "Missal calendarium: S. Didaci Conf., III classis"},
+ {"vigil-of-st-lawrence", "colour", "violet", "RG 128: III-class vigil outside Paschaltide"},
+ {"vigil-of-the-assumption", "colour", "violet", "RG 128: II-class vigil outside Paschaltide"},
+ } {
+ rc, ok := l.Cels[c.slug]
+ if !ok {
+ t.Errorf("missing %q -- if the slug was renamed upstream, the generator's "+
+ "missalOverrides entry must be renamed with it, or the correction is lost", c.slug)
+ continue
+ }
+ if got := rc.Fields[c.field]; got != c.want {
+ t.Errorf("%s: %s = %q, want %q (%s). This is a MISSAL correction that "+
+ "missalemeum does not carry -- if it reverted, the generator's "+
+ "missalOverrides table has stopped being applied", c.slug, c.field, got, c.want, c.why)
+ }
+ }
+ // The exception, asserted in the other direction: RG 128's vigil rule is
+ // "outside Paschaltide", and the Ascension's vigil is inside it.
+ if rc, ok := l.Cels["vigil-of-the-ascension"]; ok {
+ if got := rc.Fields["colour"]; got != "" && got != "white" {
+ t.Errorf("vigil-of-the-ascension: colour = %q, want white -- RG 128's "+
+ "violet rule is for vigils OUTSIDE Paschaltide and this one is inside it", got)
+ }
+ }
+}
diff --git a/internal/caldata/tridentine-calendar.ini b/internal/caldata/tridentine-calendar.ini
index 2786125..51b931a 100644
--- a/internal/caldata/tridentine-calendar.ini
+++ b/internal/caldata/tridentine-calendar.ini
@@ -8,12 +8,16 @@
; name.<lang> (missalemeum supplies English only) is preserved verbatim from
; whatever this file already carried before regeneration. See NOTICE.
;
-; NO LONGER PURELY GENERATED -- READ BEFORE RE-RUNNING THE GENERATOR.
-; Four fields below are HAND CORRECTIONS against the 1962 Missale Romanum
-; itself, adopted 2026-08-18 from the sibling project colitur, which validated
-; this data against the Missal and found missalemeum wrong on each. They are
-; NOT in missalemeum and a regeneration WILL SILENTLY REVERT THEM. Re-apply
-; them, or teach the generator to, before committing regenerated output:
+; FOUR FIELDS ARE NOT FROM missalemeum. They are corrections against the 1962
+; Missale Romanum itself, adopted 2026-08-18 from the sibling project colitur,
+; which validated this data against the Missal and found missalemeum wrong on
+; each. missalemeum does not carry them.
+;
+; REGENERATING THIS FILE IS SAFE. gen-sanctoral-ef.go re-applies all four from
+; its own missalOverrides table on every run, and fails loudly rather than
+; silently dropping one whose slug has been renamed upstream.
+; EDITING THEM HERE BY HAND IS NOT SAFE -- the next run puts them back. Change
+; the table. TestTridentineMissalOverrides pins all four either way:
;
; [ubaldus] rank commemoration -> class-3
; [didacus] rank commemoration -> class-3
diff --git a/scripts/gen-sanctoral-ef.go b/scripts/gen-sanctoral-ef.go
index 34bd184..ba4d9ce 100644
--- a/scripts/gen-sanctoral-ef.go
+++ b/scripts/gen-sanctoral-ef.go
@@ -11,10 +11,12 @@
// [vigil-of-st-lawrence] colour red -> violet
// [vigil-of-the-assumption] colour white -> violet
//
-// missalemeum does not carry them, so RUNNING THIS GENERATOR WILL SILENTLY
-// REVERT ALL FOUR. Re-apply them before committing regenerated output, or
-// teach this program to. The ini file's own header repeats this list and the
-// rubrics behind it, so neither place can rot without the other showing it.
+// missalemeum does not carry them. This generator RE-APPLIES all four on every
+// run, from the missalOverrides table below, and fails rather than warns if
+// one names a slug this run did not harvest -- so a rename upstream stops the
+// build instead of quietly dropping a correction. Change the table, not the
+// generated file. internal/caldata's TestTridentineMissalOverrides pins the
+// same four values from the other side, so neither can rot alone.
//
// gen-sanctoral-ef generates the EF (Extraordinary Form, 1962) universal
// sanctoral (internal/caldata/tridentine-calendar.ini) from missalemeum's
@@ -292,6 +294,80 @@ func fetch(date string) (*mmDay, error) {
return nil, err
}
+// missalOverride is a field where the 1962 MISSALE ROMANUM ITSELF decides
+// against missalemeum, and the Missal wins. Applied after harvesting, just
+// before the file is written.
+//
+// Why an explicit table rather than "preserve whatever the existing file had"
+// -- which is how otherNames below works, and would have been less code.
+// Preserving from the file cannot tell a deliberate correction from simple
+// staleness: if missalemeum ever fixes one of these, a preserve-from-file rule
+// would keep overriding it forever and nobody would notice. Naming each
+// override with its authority says what we mean, and makes an obsolete one
+// findable by re-reading four lines rather than by diffing a calendar.
+//
+// Each was found by the sibling project colitur, which validates this data
+// against the Missal and its Rubricae Generales rather than against another
+// calendar program. See its docs/research/rules-register.md sections 6.24 and
+// 6.25 for the full account, and internal/calendar/oracle_ef_test.go for the
+// allow-list entries these four require, six days of its 730-day window.
+type missalOverride struct {
+ slug string
+ field string // "rank" or "colour"
+ want string
+ why string
+}
+
+var missalOverrides = []missalOverride{
+ {"ubaldus", "rank", "class-3",
+ `Missal calendarium: "S. Ubaldi Ep. et Conf., III classis" (16 May), no ` +
+ `commemoration rubric, and a Mass entry of its own in the Proprium ` +
+ `taking its readings from a Common -- which a bare commemoration never has`},
+ {"didacus", "rank", "class-3",
+ `Missal calendarium: "S. Didaci Conf., III classis" (13 Nov), same shape ` +
+ `as ubaldus above`},
+ {"vigil-of-st-lawrence", "colour", "violet",
+ `RG 128: violet for vigils of II and III class outside Paschaltide. ` +
+ `9 August is a III-class vigil outside Paschaltide`},
+ {"vigil-of-the-assumption", "colour", "violet",
+ `RG 128, same rule; 14 August is a II-class vigil outside Paschaltide. ` +
+ `The rule's sole exception is the Ascension's vigil, INSIDE Paschaltide, ` +
+ `which is white and is deliberately not listed here`},
+}
+
+// applyMissalOverrides rewrites the harvested entries in place and reports
+// what it did. It FAILS rather than warns when an override names a slug that
+// is not present: a rename upstream would otherwise drop a correction
+// silently, which is the exact failure this table exists to prevent.
+func applyMissalOverrides(entries map[string]entry) error {
+ for _, o := range missalOverrides {
+ e, ok := entries[o.slug]
+ if !ok {
+ return fmt.Errorf("missal override names [%s], which this run did not "+
+ "harvest -- has the slug changed upstream? Fix the override or the "+
+ "generator; do NOT delete it silently. Reason on record: %s", o.slug, o.why)
+ }
+ var had string
+ switch o.field {
+ case "rank":
+ had, e.rank = string(e.rank), calendar.Rank(o.want)
+ case "colour":
+ had, e.colour = e.colour, o.want
+ default:
+ return fmt.Errorf("missal override for [%s]: unknown field %q", o.slug, o.field)
+ }
+ entries[o.slug] = e
+ if had == o.want {
+ fmt.Fprintf(os.Stderr, "\nmissal override [%s] %s: already %s upstream -- "+
+ "missalemeum may have fixed this; consider retiring the override\n",
+ o.slug, o.field, o.want)
+ } else {
+ fmt.Fprintf(os.Stderr, "\nmissal override [%s] %s: %s -> %s\n", o.slug, o.field, had, o.want)
+ }
+ }
+ return nil
+}
+
type entry struct {
slug, date, colour, class, en, first, gospel string
rank calendar.Rank
@@ -742,6 +818,16 @@ func main() {
}
}
+ // The Missal overrides missalemeum on four fields -- see missalOverrides
+ // above for each one's authority. Applied HERE, after every harvest and
+ // collision fix-up has settled and before anything is written, so that the
+ // value the Missal requires is the last word rather than something a later
+ // pass could overwrite.
+ if err := applyMissalOverrides(entries); err != nil {
+ fmt.Fprintf(os.Stderr, "\n%v\n", err)
+ os.Exit(1)
+ }
+
es := make([]entry, 0, len(entries))
for _, e := range entries {
e.otherNames = preservedNames[e.slug]
@@ -763,7 +849,18 @@ func main() {
b.WriteString("; Generated by scripts/gen-sanctoral-ef.go from missalemeum (Divinum Officium 1962\n")
b.WriteString("; data). name.en is always regenerated fresh from missalemeum; every other\n")
b.WriteString("; name.<lang> (missalemeum supplies English only) is preserved verbatim from\n")
- b.WriteString("; whatever this file already carried before regeneration. See NOTICE.\n\n")
+ b.WriteString("; whatever this file already carried before regeneration. See NOTICE.\n")
+ b.WriteString(";\n")
+ b.WriteString("; FOUR FIELDS ARE NOT FROM missalemeum. They are corrections against the 1962\n")
+ b.WriteString("; Missale Romanum itself, which missalemeum does not carry, re-applied on every\n")
+ b.WriteString("; run by this generator's own missalOverrides table (see its comment for the\n")
+ b.WriteString("; authority behind each). Regenerating this file is therefore SAFE and will\n")
+ b.WriteString("; not revert them; editing them here by hand is not, because the next run\n")
+ b.WriteString("; puts them back. Change the table instead.\n")
+ for _, o := range missalOverrides {
+ fmt.Fprintf(&b, "; [%s] %s = %s\n", o.slug, o.field, o.want)
+ }
+ b.WriteString("\n")
b.WriteString("[layer]\nid = tridentine\nname = General Roman Calendar of 1962\ntype = universal\n")
for _, e := range es {
fmt.Fprintf(&b, "\n[%s]\ndate = %s\nrank = %s\ncolour = %s\n", e.slug, e.date, e.rank, e.colour)