diff options
| -rw-r--r-- | CLAUDE.md | 37 | ||||
| -rw-r--r-- | bin/main.ml | 62 | ||||
| -rw-r--r-- | test/cli.t | 74 |
3 files changed, 163 insertions, 10 deletions
@@ -224,13 +224,13 @@ against missalemeum; layer 5 pins ~30 dates. ever exercised between two `Commemoration_only` candidates, neither of which has any RG 91 table position to compare in the first place. -## Current state (Plans 1–3 DONE — verify with `git log`) +## Current state (Plans 1–3 + the EF lectionary DONE — verify with `git log`) **Plans 1 + 2 are on `main` (35 commits). Plan 3 and its follow-on fix/feature tasks (RG 16(a), Holy Family/RG 112(a), Holy Name/RG 110, the Sacred Triduum, the BVM Saturday Office, the Major Litanies) have landed on a chain of feature -branches since — test count keeps climbing task by task (325 tests green, 326 -with the exhaustive sweep, as of `ef-major-litanies`; this line is not kept in +branches since — test count keeps climbing task by task (369 tests green, 370 +with the exhaustive sweep, as of `ef-lectionary`; this line is not kept in lockstep with every task, `git log`/`dune test` are the actual source of truth).** The kernel, the **complete EF temporal cycle**, the **resolution engine**, the **sanctoral data**, and **all five @@ -277,7 +277,19 @@ earlier one; M5 corrected (a prior note had 2027's own outcome backwards) and M20 added by the `ef-major-litanies` task, M18 394 not 395 accordingly). Fixtures live in `test/fixtures/` with asserted SHA-256s. -**CLI**: `colitur easter <year>`, `temporal <year>`, `day <year>`. +**CLI**: `colitur easter <year>`, `temporal <year>`, `day <year>`, +`readings <year>`. + +`readings` is a **separate command, not extra columns on `day`**, for a +mechanical reason worth not rediscovering: a citation contains spaces and +commas (`Ezech 34:11-16`, `Ecclus 51:1-8, 12`) while a `day` row is +space-separated with a variable-length `+slug` commemoration tail, so +appending them there leaves the row unsplittable by field number. `day`'s +format is therefore **byte-identical** to what it was before the lectionary +existed (asserted in `test/cli.t`). Both are a **stopgap**, not the project's +answer to output: the design still calls for one schema rendered through a +logic-less template engine — two ad-hoc formats are easier to retire than one +overloaded format with unwritten parsing rules. `band` is **provably total** over everything the engine constructs: zero `unclassified` across all 8 417 years, for a 28-branch hand-transcribed table. @@ -304,8 +316,21 @@ dune exec colitur -- day 2026 | head ## What's next -- **Plan 4 — OF rite module** (proves `RITE` generalizes) → the lectionary - bootstrap and citations → full output/rendering → hardening and a first tag. +- **The EF lectionary is DONE** (branch `ef-lectionary`, 20 commits): the four- + step reading-resolution chain, its data (`data/ef/lectionary.sexp`, + `commons.sexp`, sanctoral propers), all five validation layers extended to + citations, and `colitur readings`. Layer 2 asserts that **every day of every + year 1583–9999 resolves exactly one Epistle and one Gospel** — measured, and + mutation-proved live rather than silently inert. **Chants (Psalm, Second, + Tract, Alleluia, Sequence) remain deliberately unbuilt**: no source, no + oracle, and `Validate`'s own `citations` check now *rejects* any part outside + First/Gospel, so one appearing would be a defect rather than a feature + arriving early. +- **Plan 4 — OF rite module** (proves `RITE` generalizes) → full output/ + rendering → hardening and a first tag. (The lectionary bootstrap and + citations this line used to defer to Plan 4 landed early, on + `ef-lectionary`; what remains here is OF's own lectionary, not the + mechanism, which is now built and rite-agnostic.) **All four behaviour items below are now RESOLVED** (RG 16(a) and commemoration identity, closed on branch `ef-rg16a`; Holy Family/RG 112(a) and observed identity, closed on branch `ef-rg112-rg110`; Holy Name of diff --git a/bin/main.ml b/bin/main.ml index 16daa49..01cdbf8 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -157,6 +157,51 @@ let day_line (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kerne (Colitur_kernel.Colour.to_string cel.Colitur_kernel.Celebration.colour) commemorations +(* The reading citations for a day, as its own row shape rather than extra + columns on [day_line]'s. + + A SEPARATE COMMAND, not a widening of `colitur day`, and the reason is + mechanical rather than aesthetic: a citation contains spaces and commas + ("Ezech 34:11-16", "Ecclus 51:1-8, 12"), while [day_line]'s row is + space-separated with a variable-length "+slug" commemoration tail. + Appending citations there would leave the row unsplittable -- no [awk]/ + [cut] field number could recover where the Epistle ends -- which is the + opposite of the composability the row is shaped for. So `day` keeps its + format byte-identical (nothing downstream of it changes at all) and the + citations get a row whose own fields are " | "-delimited, safe for values + containing spaces. + + This is deliberately a stopgap, and should not be mistaken for the + project's answer to output formatting: the design calls for one schema + rendered through a logic-less template engine (CSV/JSON/S-expression), + which is where this belongs eventually. Two ad-hoc column formats are + easier to retire later than one overloaded format with parsing rules + nobody wrote down. + + "-" for an absent part, matching [temporal_report]'s own [field] + convention for an empty column. On the EF data as it stands no day can + actually print "-" -- {!Colitur_kernel.Validate}'s "citations"/ + "citations-unresolved" checks assert exactly one First and one Gospel on + every day of every year 1583..9999 -- but the CLI must not assume a + guarantee the kernel makes about DATA rather than about types. *) +let readings_line (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t) + = + let cel = d.Colitur_kernel.Liturgical_day.observed in + let part_ref p = + match + List.find_opt + (fun (c : Colitur_kernel.Citation.t) -> c.Colitur_kernel.Citation.part = p) + d.Colitur_kernel.Liturgical_day.citations + with + | Some c -> c.Colitur_kernel.Citation.reference + | None -> "-" + in + Printf.printf "%s %s | %s | %s\n" + (D.to_iso8601 d.Colitur_kernel.Liturgical_day.date) + (Colitur_kernel.Slug.to_string cel.Colitur_kernel.Celebration.slug) + (part_ref Colitur_kernel.Citation.First) + (part_ref Colitur_kernel.Citation.Gospel) + (* 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 @@ -187,7 +232,12 @@ let load_ef_data () = | Error msg -> Error msg | Ok commons -> Ok (layer, lectionary, commons))) -let day_report y = +(* The resolved-year walk, shared by [day_report] and [readings_report]: they + differ only in how each day is printed, and the two-liturgical-year + indexing below (with its own reasoning about civil-vs-liturgical spans) is + exactly the part that must not be duplicated and drift. [line] is the only + difference between the two commands. *) +let resolved_year_report ~line y = match load_ef_data () with | Error msg -> Printf.eprintf "colitur: %s\n" msg; @@ -211,7 +261,7 @@ let day_report y = let d = ref jan1 in while D.compare !d dec31 <= 0 do (match Hashtbl.find_opt by_rata (D.to_rata !d) with - | Some day -> day_line day + | Some day -> line day | None -> (* Unreachable for any [y] in 1583..9999: the two indexed liturgical years jointly cover [year_start (y-1), year_start @@ -227,8 +277,13 @@ let day_report y = d := D.add_days !d 1 done +let day_report y = resolved_year_report ~line:day_line y +let readings_report y = resolved_year_report ~line:readings_line y + let usage () = - prerr_endline "colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year>"; + prerr_endline + "colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur \ + readings <year>"; exit 2 let with_year ys f = @@ -244,4 +299,5 @@ let () = | [| _; "easter"; ys |] -> with_year ys easter_report | [| _; "temporal"; ys |] -> with_year ys temporal_report | [| _; "day"; ys |] -> with_year ys day_report + | [| _; "readings"; ys |] -> with_year ys readings_report | _ -> usage () @@ -17,7 +17,7 @@ A year outside the supported domain is rejected (exit 2): No/garbage arguments give a usage error (exit 2): $ colitur - colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> + colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> [2] The EF temporal cycle for a year, one line per day: @@ -119,3 +119,75 @@ A year outside the supported domain is rejected (exit 2): $ colitur day 1000 colitur: year 1000 out of range 1583..9999 [2] + +The reading citations for a year, one line per day: date, the observed day's +slug, then the Epistle and the Gospel, " | "-delimited. + +A SEPARATE command rather than extra columns on `colitur day`, for a +mechanical reason: a citation contains spaces and commas ("Ezech 34:11-16", +"Ecclus 51:1-8, 12"), while a `day` row is space-separated with a +variable-length "+slug" commemoration tail, so appending them there would +leave the row unsplittable by field number. `day`'s own format is therefore +byte-identical to what it was before readings existed -- asserted directly, +below. + + $ colitur readings 2026 | head -3 + 2026-01-01 ef-circumcision | Titus 2:11-15 | Luke 2:21 + 2026-01-02 ef-christmas-1-friday | Titus 2:11-15 | Luke 2:21 + 2026-01-03 ef-christmas-1-saturday | Gal 4:1-7 | Luke 2:33-40 + + $ colitur readings 2026 | wc -l + 365 + +2 January reads the Circumcision's own Mass, not its own: the Missal's +Mass-propers rubric printed under the Sanctissimi Nominis Iesu heading -- +"Diebus ferialibus a 2 ad 5 ianuarii Missa dicitur ut die 1 ianuarii, cum +Gloria et praefatione de Nativitate, sine Credo et Communicantes proprio" +(docs/research/scan1.txt:6523-6526, corroborated scan2.txt:7216). Note this +is NOT RG 17(a), which fixes only WHEN the Holy Name is kept; the two are +different rules sharing a date window. Hence the identical citation on the +1st and the 2nd above, which is correct and not a duplicated row. + +One line per step of the reading-resolution chain, the same four cases +test_golden.ml pins against the photographic scans (each literal there was +read out of the Missal before being compared with colitur -- these assert +that the CLI path agrees with the library path, nothing more): + + $ colitur readings 2038 | grep '^2038-03-08 ' + 2038-03-08 john-of-god | Ecclus 31:8-11 | Matt 22:34-46 + + $ colitur readings 2026 | grep '^2026-02-23 ' + 2026-02-23 ef-lent-1-monday | Ezech 34:11-16 | Matt 25:31-46 + + $ colitur readings 2025 | grep '^2025-12-01 ' + 2025-12-01 ef-advent-1-monday | Rom 13:11-14 | Luke 21:25-33 + + $ colitur readings 2038 | grep '^2038-03-06 ' + 2038-03-06 sts-felicitas-perpetua | Ecclus 51:1-8, 12 | Matt 13:44-52 + +The Advent line above is the ferial resumption (chain step 3) and is the one +worth reading twice: 1 December 2025 is the Monday after Advent I, and +Advent's ferias -- unlike Lent's -- have no Mass of their own, so the +citation shown IS Advent I Sunday's ("ad Romanos. Rom. 13, 11-14" / +"dum Lucam. Luc. 21, 25-33", scan1.txt:4878 + 4912). A regression that +stopped walking back would change this line and nothing else. + +Every day of the year carries both parts -- no "-" anywhere. This is the CLI +view of the invariant Validate asserts over the whole 1583..9999 domain +("citations" / "citations-unresolved"), so it is a spot-check of one year +rather than the guarantee itself: + + $ colitur readings 2026 | grep -c ' | - \| | -$' + 0 + [1] + +`colitur day`'s own format is unaffected by any of the above: + + $ colitur day 2026 | head -1 + 2026-01-01 thursday christmastide - ef-circumcision class-1 white + +A year outside the supported domain is rejected (exit 2): + + $ colitur readings 1000 + colitur: year 1000 out of range 1583..9999 + [2] |
