aboutsummaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-24 16:29:23 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-24 16:29:23 +0200
commit8a5da8756cf6b57aa36c01b71ce893f7967d3a29 (patch)
tree10873a692400fa25ce54c67ac9fc34d46af86272 /bin
parent4f87cbde4ee79ac96aa7ebfed105b3a5ec02be43 (diff)
parent73b15551804bb63ee0081005e2869d36afb54be2 (diff)
downloadcolitur-8a5da8756cf6b57aa36c01b71ce893f7967d3a29.tar.gz
colitur-8a5da8756cf6b57aa36c01b71ce893f7967d3a29.zip
merge: celebrant Mass rubrics, and four rubrical corrections
colitur now prints what a real ordo prints for the Mass: which formulary is said and how it was reached, the Gloria, the Creed, the preface, and the commemorations with their Low-Mass/sung distinction. colitur rubrics joins day and readings. Four defects were found and fixed on the way, each by an external witness rather than by inspection: the Creed said at Requiem Masses (RG 476(f)), the missing bissextile shift of St Matthias and St Gabriel (2 041 leap years), Rogation Monday and Tuesday coloured violet in Paschaltide (RG 119(b)), and the ferias after the Ascension resuming the wrong Sunday's Mass rather than the Ascension's. Validation gained a sixth layer and then some: the preface is checked against three independent publishers over seven witness-years (FIUV, three LMS editions, three extraordinaryform.org editions), none of which shares the Divinum Officium -> missalemeum -> lectio lineage the older layers all descend from.
Diffstat (limited to 'bin')
-rw-r--r--bin/main.ml137
1 files changed, 130 insertions, 7 deletions
diff --git a/bin/main.ml b/bin/main.ml
index 18fd2f5..b5d8d98 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -362,6 +362,91 @@ let readings_line ~lang ~sigla (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.r
(part_ref Colitur_kernel.Citation.Gospel)
name_suffix
+(* Task 4 (celebrant-rubrics-phase1): the day's own Mass formulary -- which
+ slug's Mass is actually said, and how that was decided
+ ({!Colitur_kernel.Mass_formulary.source}: proper/own/preceding-sunday/
+ common/votive).
+
+ A SEPARATE command from `day` and `readings`, for the identical mechanical
+ reason CLAUDE.md already records for `readings`: a formulary NAME (not
+ built yet, but the reason this row is shaped the way it is) contains
+ spaces ("Mass of the 9th Sunday after Pentecost"), and `day`'s own row is
+ fixed-width space-separated with a variable-length "+slug" tail, so
+ appending anything with its own internal whitespace there would leave the
+ row unsplittable by field number.
+
+ TAB-separated rather than reusing [readings_line]'s " | " -- deliberately
+ a THIRD delimiter, not a second use of the existing one -- because a
+ later column on this row (the resolved formulary name, once one exists)
+ can itself contain a literal "|" inside punctuation a citation never
+ does, and because TAB is what stays unambiguous once a field may carry
+ both spaces and arbitrary punctuation. This is also why `rubrics` does
+ not (yet) take --lang/--raw/--sigla-*: there is no display name or
+ citation on this row for any of them to resolve.
+
+ [d.formulary] is documented as [Some] on every day of every year for EF,
+ asserted by {!Colitur_kernel.Validate}'s own ["formulary"] check -- but
+ the type itself permits [None] (a rite with no lectionary), so this
+ prints "-" rather than pattern-matching partially and crashing on a
+ guarantee that belongs to DATA, not to the type.
+
+ Task 5 (celebrant-rubrics-phase1): a fourth column, whether the Creed is
+ said (EF: RG 475-476, {!Rite_ef.Rubrics_ef.creed}) -- "true"/"false"
+ ([string_of_bool], not "yes"/"no" or "1"/"0": this row has no other
+ boolean column to be consistent with, so OCaml's own literal is the
+ least surprising choice for a machine-readable field). Unlike
+ [formulary], [d.creed] is a plain [bool] with no [option] to guard: a
+ rite that has not implemented the rule answers [false] outright, so
+ there is no third "unknown" state this column could ever need to print.
+
+ Whole-branch review fix round: {!Colitur_kernel.Mass_formulary.t.said}
+ itself gained an [option] (its own citation has the full account --
+ [None] exactly for [Votive], where the shipped data genuinely names no
+ slug for the Mass actually said). This column's own OUTPUT does not
+ change for that reason: when [said] is [None] it falls back to
+ [d.observed]'s own slug -- the SAME value this column always printed
+ for a [Votive] day before [said] became honest, and it is a value this
+ function already has in scope regardless of [via]. So this is not
+ "print a placeholder for the missing case", it is "the value was
+ already available from a different field, and still is".
+
+ Task (celebrant-rubrics-phase1, Phase 2): a FIFTH column, whether the
+ Gloria in excelsis is said (EF: RG 431-432, {!Rite_ef.Rubrics_ef.gloria})
+ -- same [string_of_bool] convention as [creed], same plain [bool] with
+ no [option] to guard, same reasoning throughout.
+
+ Task (celebrant-rubrics-phase1, Phase 3): a SIXTH column, which preface
+ is said (EF: RG 482-499, {!Rite_ef.Rubrics_ef.preface}) --
+ {!Colitur_kernel.Preface.to_string} (e.g. "common", "holy-cross"), or
+ "-" when [d.preface] is [None]. UNLIKE [creed]/[gloria], [preface] is a
+ genuine [option]: "-" here can mean either of two things ("this rite
+ has not implemented the rule" or "this specific day has no Mass to
+ preface", {!Colitur_kernel.Rite.t.preface}'s own citation) and this
+ column does not distinguish them, the same "-" convention [formulary]'s
+ own [None] case already uses two columns to the left, for the identical
+ reason. *)
+let rubrics_line (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t)
+ =
+ let said, via =
+ match d.Colitur_kernel.Liturgical_day.formulary with
+ | Some f ->
+ ( Colitur_kernel.Slug.to_string
+ (match f.Colitur_kernel.Mass_formulary.said with
+ | Some s -> s
+ | None -> d.Colitur_kernel.Liturgical_day.observed.Colitur_kernel.Celebration.slug),
+ Colitur_kernel.Mass_formulary.source_to_string f.Colitur_kernel.Mass_formulary.via )
+ | None -> ("-", "-")
+ in
+ let preface =
+ match d.Colitur_kernel.Liturgical_day.preface with
+ | Some p -> Colitur_kernel.Preface.to_string p
+ | None -> "-"
+ in
+ Printf.printf "%s\t%s\t%s\t%s\t%s\t%s\n" (D.to_iso8601 d.Colitur_kernel.Liturgical_day.date) said via
+ (string_of_bool d.Colitur_kernel.Liturgical_day.creed)
+ (string_of_bool d.Colitur_kernel.Liturgical_day.gloria)
+ preface
+
(* One civil year, Jan 1 - Dec 31, matching [temporal_report]'s own scan --
NOT one liturgical year: [Colitur_kernel.Calendar.year] resolves a single
Advent-anchored liturgical year, which straddles two civil years, so a
@@ -456,6 +541,7 @@ let resolved_year_report ~line ~overlays y =
let day_report ~lang ~overlays y = resolved_year_report ~line:(day_line ~lang) ~overlays y
let readings_report ~lang ~sigla ~overlays y =
resolved_year_report ~line:(readings_line ~lang ~sigla) ~overlays y
+let rubrics_report ~overlays y = resolved_year_report ~line:rubrics_line ~overlays y
(* Task 8: `colitur emit` -- the five template-family emitters built in
Tasks 5-7, wired to a year RANGE rather than a single year, because a
@@ -1182,7 +1268,9 @@ usage:
colitur temporal <year> the temporal cycle, one line per day
colitur day <year> the resolved day identity, one line per day
colitur readings <year> the Mass reading citations, one line per day
+ colitur rubrics <year> the Mass formulary said, one line per day
colitur day|readings <year> [--overlay FILE ...] [--lang CODE|FILE] [--raw]
+ colitur rubrics <year> [--overlay FILE ...]
colitur emit --format csv|json|sexp|xml|ics --from Y --to Y
[--overlay FILE ...] [--dtstamp S] [--lang CODE|FILE] [--raw]
render a resolved year range through one of five emitters
@@ -1216,6 +1304,8 @@ output formats:
2026-04-05 sunday paschaltide 1 ef-easter-sunday class-1 white
readings date slug | Epistle | Gospel [| name]
2026-12-25 ef-nativity | Heb 1:1-12 | John 1:1-14
+ rubrics date, formulary slug, source, creed, gloria, preface -- TAB-separated
+ 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true[TAB]nativity
A citation contains spaces, so readings uses " | " between its fields while
day stays space-separated; that is why they are separate commands rather
@@ -1228,6 +1318,25 @@ output formats:
for that particular day, the trailing field is simply absent, which is
what makes --raw byte-identical to this program's pre-naming output.
+ rubrics prints which Mass is actually said and how that was decided
+ (source: proper/own/preceding-sunday/common/votive) -- not always the
+ day's own: a weekday with no proper resumes the preceding Sunday's, a
+ saint with no proper says his assigned Common -- followed by whether the
+ Creed is said (RG 475-476), whether the Gloria in excelsis is said
+ (RG 431-432, deferring to the Breviary's own Te Deum rule, nn. 237-238,
+ for RG 431(a)), and which preface is said (RG 482-499) -- Creed/Gloria
+ both "true"/"false", OCaml's own literal, not "yes"/"no" or "1"/"0";
+ preface one of nativity/epiphany/lent/holy-cross/easter/ascension/
+ sacred-heart/christ-the-king/holy-spirit/trinity/bvm/st-joseph/apostles/
+ common/requiem, or "-" when this engine resolves no Mass at all that day
+ (Good Friday). TAB-separated rather than space or " | ": a resolved
+ formulary NAME is a column a later version may add, and it can carry
+ both spaces and punctuation a citation never does, which rules out
+ either alternative already in use above. --overlay is accepted (the
+ observed celebration it changes decides the formulary, the Creed, the
+ Gloria and the preface); --lang/--raw/--sigla-* are refused -- this row
+ resolves no display name and no citation for any of them to affect.
+
emit one schema (season, week, slug, rank, colour, subject, names,
citations, commemorations), rendered five ways: csv (RFC 4180,
one header for the whole run), json, sexp, xml (schema/colitur-
@@ -1240,9 +1349,10 @@ overlays:
the shipped universal one, never instead of it, so local feasts
add to it rather than replacing it. Later files win over earlier
ones, and over the universal calendar, when they name the same
- slug. Accepted on `day`, `readings`, `emit`, `table`, `render` and
- `publish` -- `easter` and `temporal` read no sanctoral data, so
- the flag is refused there rather than silently ignored.
+ slug. Accepted on `day`, `readings`, `rubrics`, `emit`, `table`,
+ `render` and `publish` -- `easter` and `temporal` read no
+ sanctoral data, so the flag is refused there rather than
+ silently ignored.
An overlay is applied, NOT validated: colitur's test layers assert
things about the shipped calendar and cannot vouch for a file you
@@ -1435,10 +1545,10 @@ let print_help () =
let usage () =
prerr_endline
"colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur \
- readings <year> | colitur emit --format FMT --from Y --to Y | colitur table --year Y --template \
- FILE | colitur render --template FILE --year Y | colitur publish --from Y --to Y --out DIR | \
- colitur lang --list|--dump CODE|--check FILE | colitur config --show | colitur check FILE | \
- colitur new-overlay (try: colitur --help)";
+ readings <year> | colitur rubrics <year> | colitur emit --format FMT --from Y --to Y | colitur \
+ table --year Y --template FILE | colitur render --template FILE --year Y | colitur publish \
+ --from Y --to Y --out DIR | colitur lang --list|--dump CODE|--check FILE | colitur config --show \
+ | colitur check FILE | colitur new-overlay (try: colitur --help)";
exit 2
let with_year ys f =
@@ -2269,6 +2379,19 @@ let () =
~sigla_tradition_flag:sigla_tradition ~config
in
with_year ys (readings_report ~lang:lang_t ~sigla ~overlays:effective_overlays)
+ | [ "rubrics"; ys ] ->
+ (* --overlay accepted, same reasoning as `readings`: an overlay can
+ change which celebration is observed, hence which Mass formulary
+ is said. --lang/--raw/--sigla-* are refused, unlike `readings`
+ -- this row resolves no display name and no citation for any of
+ them to affect. *)
+ reject_emit "rubrics";
+ reject_table "rubrics";
+ reject_publish "rubrics";
+ reject_lang "rubrics";
+ reject_lang_sub "rubrics";
+ reject_sigla "rubrics";
+ with_year ys (rubrics_report ~overlays:effective_overlays)
| [ "emit" ] -> (
reject_table "emit";
reject_publish "emit";