From 051716cdee0197351fa966af1e13738c443045c4 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 18 Aug 2026 09:26:26 +0200 Subject: feat(data): ship a worked example overlay calendar `--overlay` shipped last week with nothing to point at. The only example was a test fixture, explicitly labelled invented and living where no user would find it, so "colitur supports local calendars" was true but undemonstrated. data/ef/examples/diocesan-example.sexp is installed to /share/colitur/examples/ and is runnable documentation: every celebration in it is invented, with example- slugs that cannot collide, and its header explains the four directives, the three date shapes, and the field vocabularies before showing any of them. It demonstrates the range deliberately rather than the minimum: a fixed-date local patron with its own propers; a movable dedication on the first Sunday of October, I class because a church's own dedication anniversary is I class in that church and at III it would lose to the Sunday every year; an Easter-relative commemoration; an Edit raising a universal feast for local use; and a Suppress shown commented out, because suppressing a universal feast is a real liturgical act and an example should not invite it casually. Two things the header says plainly, since this is the one file that lets someone change what colitur computes: an overlay is applied ON TOP of the shipped calendar and never instead of it, and an overlay is applied, NOT validated -- the five test layers assert things about the shipped data and cannot vouch for a user's file. A test asserts it loads against the real EF rank vocabulary, still demonstrates at least four directives, and applies with NO diagnostics. The last matters most: a directive naming a slug the universal calendar lacks would warn, and an example that warns teaches the wrong lesson. It deliberately does not pin which days it produces -- those celebrations are invented, and pinning their dates would make an illustrative file behave like calendar data. --- data/dune | 7 ++- data/ef/examples/diocesan-example.sexp | 102 +++++++++++++++++++++++++++++++++ man/colitur.1 | 15 ++++- test/cli.t | 5 ++ test/dune | 1 + test/test_rite_ef.ml | 31 +++++++++- 6 files changed, 157 insertions(+), 4 deletions(-) create mode 100644 data/ef/examples/diocesan-example.sexp diff --git a/data/dune b/data/dune index 07e5307..aec4b3c 100644 --- a/data/dune +++ b/data/dune @@ -20,4 +20,9 @@ (ef/sanctoral.sexp as ef/sanctoral.sexp) (ef/adjustments.sexp as ef/adjustments.sexp) (ef/lectionary.sexp as ef/lectionary.sexp) - (ef/commons.sexp as ef/commons.sexp))) + (ef/commons.sexp as ef/commons.sexp) + ; Shipped as runnable documentation, NOT as calendar data: every + ; celebration in it is invented, with example- slugs. It is what + ; `colitur --overlay` points at in the man page, so it must actually be + ; installed rather than only living in the source tree. + (ef/examples/diocesan-example.sexp as examples/diocesan-example.sexp))) diff --git a/data/ef/examples/diocesan-example.sexp b/data/ef/examples/diocesan-example.sexp new file mode 100644 index 0000000..65acf3a --- /dev/null +++ b/data/ef/examples/diocesan-example.sexp @@ -0,0 +1,102 @@ +; ============================================================================ +; An EXAMPLE overlay calendar for `colitur --overlay`. +; +; This file is shipped as documentation you can run, not as calendar data. +; Every celebration in it is INVENTED, with slugs prefixed `example-` so they +; cannot collide with anything real. Copy it, gut it, and put your own diocese +; or parish in its place. +; +; colitur day 2026 --overlay /share/colitur/examples/diocesan-example.sexp +; +; WHAT AN OVERLAY IS +; +; A list of directives applied ON TOP of the shipped 1962 universal calendar, +; never instead of it. colitur applies its own data/ef/adjustments.sexp first, +; then each --overlay in the order given. Later writers win, so a local +; calendar can deliberately override a universal entry by naming its slug. +; +; AN OVERLAY IS APPLIED, NOT VALIDATED. colitur's five test layers assert +; things about the SHIPPED calendar; none of them can vouch for this file or +; yours. A directive naming a slug that does not exist prints a warning on +; stderr and the run continues, so a typo is visible rather than silent; a +; file that fails to parse is fatal. +; +; THE FOUR DIRECTIVES +; +; (Add ) a celebration the universal calendar lacks +; (Suppress ) remove one it has +; (Replace ) swap one out wholesale +; (Edit ()) change named fields only +; +; DATES +; +; (Fixed (month 7) (day 11)) a civil date, every year +; (Easter_offset -9) signed days from Easter +; (Nth_weekday (month 10) (nth 1) (weekday Sun)) nth weekday of a month; +; negative nth counts back, +; so (nth -1) is the last +; +; RANKS are Class1..Class4; STATUS is Feast or Commemoration_only; COLOUR is +; White Red Green Violet Rose Black; SUBJECT is Lord Bvm Saint Temporal. +; NAMES are keyed by language code and any set of languages is allowed. +; CITATIONS are reading references, never scripture text; omit them and the +; day falls through to whatever the resolution chain finds. +; ============================================================================ +((id diocesan-example) + (directives + ( + + ; ---- 1. A fixed-date local patron, with its own proper readings ------- + ; The commonest thing a local calendar adds. III class is the usual rank + ; for a diocesan patron who is not the cathedral's titular. + (Add + ((date (Fixed (month 7) (day 11))) + (cel + ((slug example-local-patron) + (names ((la "Sancti Exempli Patroni") (en "St Example, Patron") + (pl "\197\154w. Przyk\197\130adu, Patrona"))) + (rank Class3) (status Feast) (colour White) (subject Saint) + (citations (((part First) (reference "Ecclus 45:1-6")) + ((part Gospel) (reference "Matt 25:14-23")))) + (layer diocesan-example))))) + + ; ---- 2. A MOVABLE feast: the first Sunday of October ------------------ + ; The anniversary of a church's own dedication is I class IN THAT CHURCH, + ; which is why this one outranks and displaces the Sunday it lands on -- + ; at III class it would lose to a II-class Sunday every year. + (Add + ((date (Nth_weekday (month 10) (nth 1) (weekday Sun))) + (cel + ((slug example-dedication) + (names ((la "Dedicatio Ecclesiae Exempli") + (en "Dedication of the Example Church"))) + (rank Class1) (status Feast) (colour White) (subject Saint) + (citations (((part First) (reference "Apoc 21:2-5")) + ((part Gospel) (reference "Luke 19:1-10")))) + (layer diocesan-example))))) + + ; ---- 3. An EASTER-RELATIVE local commemoration ------------------------ + ; Easter+61 is the Friday after Corpus Christi. A commemoration never + ; wins the day; it is added to whatever office the day already has, + ; subject to the admission limits of RG 111. + (Add + ((date (Easter_offset 61)) + (cel + ((slug example-local-commemoration) + (names ((la "Commemoratio Exempli") (en "A Local Commemoration"))) + (rank Class4) (status Commemoration_only) (colour White) + (subject Saint) (citations ()) (layer diocesan-example))))) + + ; ---- 4. RAISING a universal feast for local use ----------------------- + ; A saint the whole Church keeps at III class may be the patron of your + ; diocese and kept higher. Edit changes only the fields named; everything + ; else, including the date and the readings, stays as shipped. + (Edit stanislaus ((Set_rank Class1) (Set_colour Red))) + + ; ---- 5. SUPPRESSING one ----------------------------------------------- + ; Shown for completeness and commented out, because suppressing a + ; universal feast is a real liturgical act and should be a deliberate + ; one. Uncomment only if your own calendar genuinely omits it. + ; + ; (Suppress example-not-kept-here) + ))) diff --git a/man/colitur.1 b/man/colitur.1 index a18b299..8a52858 100644 --- a/man/colitur.1 +++ b/man/colitur.1 @@ -160,8 +160,15 @@ in days, or an .I Nth_weekday of a month \(em so a patronal feast on "the first Sunday of October" or a dedication anniversary reckoned from Easter are both expressible. See -.I test/fixtures/overlay\-example\-diocesan.sexp -in the source distribution for a worked example of both shapes. +.I /share/colitur/examples/diocesan\-example.sexp +for a worked, runnable example of all four directives and all three date +shapes: +.RS +.nf + +.B colitur day 2026 \-\-overlay /share/colitur/examples/diocesan\-example.sexp +.fi +.RE .PP Accepted on .B day @@ -199,6 +206,10 @@ mode this program refuses. The installed calendar data: the sanctoral calendar, its one hand\-authored overlay, the temporal lectionary and the Commons. Four S\-expression files. .TP +.I /share/colitur/examples/diocesan\-example.sexp +A worked example overlay, shipped as runnable documentation. Every +celebration in it is invented; copy it and put your own calendar in its place. +.TP .I /../data/ef/ The build\-tree location, used when running from a source checkout. .PP diff --git a/test/cli.t b/test/cli.t index 39354c6..0c79db4 100644 --- a/test/cli.t +++ b/test/cli.t @@ -336,3 +336,8 @@ An unknown option is rejected rather than treated as a positional word: colitur: unknown option --diocese colitur: usage: colitur easter | colitur temporal | colitur day | colitur readings (try: colitur --help) [2] + +The shipped example overlay is runnable documentation, and it must actually +load -- an example that silently rotted would be worse than none. The cram +sandbox cannot reach data/, so the assertion that it loads and applies lives +in test_lectionary_ef.ml, which reads it from the source tree directly. diff --git a/test/dune b/test/dune index fb5127f..1e58c26 100644 --- a/test/dune +++ b/test/dune @@ -8,6 +8,7 @@ ../data/ef/expected-divergences-missalemeum.sexp ../data/ef/lectionary.sexp ../data/ef/commons.sexp + ../data/ef/examples/diocesan-example.sexp fixtures/lectio-ef-2005-2050.txt fixtures/missalemeum-ef-2026-2027.txt fixtures/missalemeum-ef-2038.txt diff --git a/test/test_rite_ef.ml b/test/test_rite_ef.ml index 4da6d73..f4991f3 100644 --- a/test/test_rite_ef.ml +++ b/test/test_rite_ef.ml @@ -526,9 +526,38 @@ let test_maurice_thomas_band_fidelity_end_to_end () = [ "maurice-and-companions-martyrs" ] (List.map (fun (c, _) -> slug_of c) day.LD.omitted) + +(* The SHIPPED example overlay (data/ef/examples/diocesan-example.sexp) is + documentation a user is invited to run and copy, and the man page names its + installed path. An example that silently stopped loading -- a directive + renamed, a Date_spec variant changed -- would be worse than shipping none, + because it would fail only for whoever tried it. + + This asserts it PARSES against the REAL EF rank vocabulary and that it + still demonstrates the range it claims to. It deliberately does NOT assert + which days it produces: those celebrations are invented, and pinning their + dates would make an illustrative file behave like calendar data. *) +let test_shipped_example_overlay_loads () = + let path = "../data/ef/examples/diocesan-example.sexp" in + match Overlay.load Rite_ef.Vocab_ef.rank_of_sexp path with + | Error e -> Alcotest.failf "the shipped example overlay must load: %s" e + | Ok o -> + Alcotest.(check string) "its id" "diocesan-example" o.Overlay.id; + Alcotest.(check bool) "it demonstrates at least four directives" true + (List.length o.Overlay.directives >= 4); + (* Applying it must be clean: a directive naming a slug the universal + calendar lacks would warn, and an EXAMPLE that warns teaches the + wrong lesson. *) + let layer = real_layer () in + let _, diagnostics = Overlay.apply layer o in + Alcotest.(check (list string)) "it applies with no diagnostics" [] + (List.map Overlay.diagnostic_to_string diagnostics) + let suite = ( "Rite_ef (real data: overlay-in-effect, domain-ceiling)", - [ Alcotest.test_case "the overlay suppression is observably in effect" `Quick + [ Alcotest.test_case "the shipped example overlay loads and applies cleanly" `Quick + test_shipped_example_overlay_loads; + Alcotest.test_case "the overlay suppression is observably in effect" `Quick test_vigil_of_christmas_suppressed; Alcotest.test_case "RG16(a) task: the two subject retags are in effect, the other four untouched" `Quick test_rg16a_subject_retag_in_effect; -- cgit v1.3