From 152bba4c94060ea2ac7430dfdf826450ee4f2bd8 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 17:29:44 +0200 Subject: docs(citation): sigla flags, book naming and traditions Includes that --raw emits citations verbatim, which is the behaviour someone diffing against lectio depends on. --- man/colitur.1 | 380 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 375 insertions(+), 5 deletions(-) (limited to 'man') diff --git a/man/colitur.1 b/man/colitur.1 index df7ad3e..4a3c580 100644 --- a/man/colitur.1 +++ b/man/colitur.1 @@ -11,6 +11,9 @@ colitur \- deterministic liturgical calendar and lectionary engine (Roman rite, .I YEAR .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-lang " CODE\(brFILE" ] +.RB [ \-\-sigla\-style " CODE\(brFILE" ] +.RB [ \-\-sigla\-book " full\(brabbr" ] +.RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur @@ -21,6 +24,9 @@ colitur \- deterministic liturgical calendar and lectionary engine (Roman rite, .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-dtstamp " STAMP" ] .RB [ \-\-lang " CODE\(brFILE" ] +.RB [ \-\-sigla\-style " CODE\(brFILE" ] +.RB [ \-\-sigla\-book " full\(brabbr" ] +.RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur @@ -30,6 +36,9 @@ colitur \- deterministic liturgical calendar and lectionary engine (Roman rite, .RB [ \-\-flavour " FLAVOUR" ] .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-lang " CODE\(brFILE" ] +.RB [ \-\-sigla\-style " CODE\(brFILE" ] +.RB [ \-\-sigla\-book " full\(brabbr" ] +.RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur @@ -41,6 +50,9 @@ colitur \- deterministic liturgical calendar and lectionary engine (Roman rite, .RB [ \-\-prune ] .RB [ \-\-dtstamp " STAMP" ] .RB [ \-\-lang " CODE\(brFILE" ] +.RB [ \-\-sigla\-style " CODE\(brFILE" ] +.RB [ \-\-sigla\-book " full\(brabbr" ] +.RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur @@ -188,7 +200,8 @@ below. .TP .B config \-\-show Print every effective setting \(em -.IR lang ", " overlay ", " template ", " format +.IR lang ", " overlay ", " template ", " format ", " sigla_style ", " +.IR sigla_book " and " sigla_tradition \(em its resolved value, and where it came from: .BR flag ", " config " or " default . See @@ -290,11 +303,60 @@ what is available, never a silent fallback to Latin. See .B NAMING below. .TP +.BI \-\-sigla\-style " CODE\(brFILE" +.RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" +Which punctuation/abbreviation convention to render a Mass reading citation +in \(em looked up exactly as +.B \-\-lang +is (a +.I CODE +against the installed language directory, or a literal path). Default the +resolved +.BR \-\-lang , +overridable by a config file. See +.B SIGLA +below. +.TP +.BI \-\-sigla\-book " full\(brabbr" +.RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" +Which form of the book name a citation uses. Default +.BR abbr , +overridable by a config file. An unrecognised value is a hard error, the +same discipline an unknown +.B \-\-lang +gets. See +.B SIGLA +below. +.TP +.BI \-\-sigla\-tradition " NAME" +.RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" +Which numbering tradition a citation's book DENOTES \(em a section name in +.BR lang/traditions.ini . +Default +.BR vulgate , +overridable by a config file. Unlike +.B \-\-sigla\-book +and +.BR \-\-lang , +an unrecognised value is +.I not +fatal: it degrades to +.B vulgate +with a warning on standard error, because asking for a renumbering is +optional the way asking for a language is not. See +.B SIGLA +below. +.TP .B \-\-raw .RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" Restore the pre\-naming output: every display name equals its bare machine -slug. See +slug, and every reading citation is emitted +.B verbatim +\(em exactly as stored, bypassing the parser, the style, the book form and +the tradition entirely. See .B NAMING +and +.B SIGLA below. .TP .BR \-h ", " \-\-help @@ -361,7 +423,10 @@ rather than extra columns on .BR day : appended there, no field number could recover where the Epistle ended. A .B \- -in either citation field means none was resolved. The resolved display +in either citation field means none was resolved. A citation's book names, +punctuation and numbering are all configurable \(em see +.B SIGLA +below. The resolved display .I name is appended as a fourth, .RB \(lq " | " \(rq \-delimited @@ -931,6 +996,12 @@ under .B \-\-raw are byte\-identical to this program's own pre\-naming output; every existing script built against that output therefore needs one flag, not a rewrite. +.B \-\-raw +also governs every reading citation, through a dedicated +.I verbatim +path rather than an identity language table \(em see +.B SIGLA +below for why that distinction matters. .TP .B lang \-\-list .TQ @@ -969,7 +1040,8 @@ merely a hand\-picked sample. .TP .B config \-\-show Print every effective setting \(em -.IR lang ", " overlay ", " template ", " format +.IR lang ", " overlay ", " template ", " format ", " sigla_style ", " +.IR sigla_book " and " sigla_tradition \(em its resolved value, and where it came from: .BR flag ", " config " or " default , via the same resolver every other command uses (there is deliberately no @@ -977,7 +1049,305 @@ separate "provenance" function, so the two cannot disagree). Also prints the config file's own path and whether it exists. See .BR colitur\-config (5) for the file's location, its precedence in full, and every setting it -recognises. +recognises (as of this writing that page still describes the original four; +.BR sigla_style / sigla_book / sigla_tradition +are the same +.B [defaults] +mechanism, documented in full here in +.B SIGLA +below). +.SH SIGLA +A Mass reading citation +.RB ( "Jn 3:16" ) +is parsed into structure \(em book, chapter, verses \(em and re\-rendered, so +its book names, its punctuation and abbreviation convention, and its +numbering tradition are each a file a reader can edit, not something baked +into the engine. This section covers the three flags, the two language\-file +sections that drive them, and +.IR lang/traditions.ini . +.SS Two different questions +Getting a citation right involves two independent questions that are easy +to conflate: +.RS +.nf + +what is the book CALLED? -- a language file's [bible] section +what book does it DENOTE? -- lang/traditions.ini +.fi +.RE +.PP +Naming varies by language: the third book of Kings is +.I "Liber Regum III" +in +.I lang/la.ini +and +.I "3 Kings" +in +.IR lang/en.ini . +Denoting does not: "modern numbering" renumbers the SAME book the SAME way +whether the citation is rendered in Latin, English or any other language, so +it lives in one file, not one section per language. Conflating the two is +how a citation ends up naming the +.I wrong +book \(em correct punctuation, correct language, wrong reference. +.SS "[sigla] -- how a citation is written" +A language file's +.I [sigla] +section is a citation +.I style : +.RS +.nf + +book = abbr ; or full +book_sep = " " +chapter_verse = {chapter}:{verses} +range = {first}\-{last} +part_sep = "; " +verse_sep = ", " +.fi +.RE +.PP +.BR book " and " book_sep +control the book name and what separates it from the reference proper. +.BR chapter_verse " and " range +are templates: the placeholders +.BR {chapter} ", " {verses} " (in " chapter_verse ) +and +.BR {first} ", " {last} " (in " range ) +are substituted; an unrecognised +.B {placeholder} +is left in the output literally, so a typo in a hand\-written style file is +visible rather than silently swallowed. +.B {chapter_roman} +is also available in +.BR chapter_verse , +an alternative to +.B {chapter} +that prints the chapter as a Roman numeral \(em set +.RI ( "chapter_verse = {chapter_roman}, {verses}" ) +and the Missal's own idiom +("Feria IV", "Hebdomada I") extends to citations too: +.RS +.nf + +$ colitur readings 2026 | grep 2026\-06\-21 +2026\-06\-21 ef\-time\-after\-pentecost\-sunday\-4 | Rom 8:18\-23 | Luc 5:1\-11 | \e +Dominica IV post Pentecosten +$ colitur readings 2026 \-\-sigla\-style my\-roman\-style.ini | grep 2026\-06\-21 +2026\-06\-21 ef\-time\-after\-pentecost\-sunday\-4 | Rom VIII, 18\-23 | Luc V, 1\-11 | \e +Dominica IV post Pentecosten +.fi +.RE +.PP +(where +.I my\-roman\-style.ini +carries only +.RI ( "chapter_verse = {chapter_roman}, {verses}" ) +under its own +.IR [sigla] ). +.PP +.BR part_sep " and " verse_sep +separate multiple readings within one citation and multiple verse ranges +within one reading, respectively \(em what makes +.I "Ecclus 51:1\-8, 12" +and +.I "Ioel 2:23\-24; 2:26\-27" +render correctly. +.PP +.B "book_sep and typeset output." +A booklet rendered to LaTeX or Typst may want a +.B non\-breaking +space here, so a line break can never fall between the book abbreviation and +its reference (\(lqLuc.\(rq stranded at the end of one line, \(lq5, 12\-14\(rq +starting the next). Set +.B book_sep +to a literal +.B U+00A0 +character \(em typed directly into the INI file, not a LaTeX tie +.RB ( "~" ) : +the flavour escapers match ASCII bytes only, so a real U+00A0 (a two\-byte +UTF\-8 sequence) passes through every flavour untouched, but a literal +.B "~" +does +.I not +survive the LaTeX escaper, which turns it into +.BR \etextasciitilde{} . +.B "U+00A0 is invisible in a terminal" \(em +it looks exactly like an ordinary space in an editor, in +.BR "cat colitur.ini" , +and in a diff that does not mark whitespace \(em so a careless copy\-paste +can silently replace it with a normal space, or vice versa. Verify what is +actually in the file, not what it looks like: +.RS +.nf + +$ grep \-o 'book_sep.*' my\-style.ini | xxd | head \-1 +00000000: 626f 6f6b 5f73 6570 203d 2022 c2a0 220a book_sep = "... +.fi +.RE +.PP +.RB ( c2 " " a0 +is U+00A0 in UTF\-8; +.B 20 +would be a plain space instead.) +.SS "[bible] -- what a book is called" +A language file's +.I [bible] +section supplies every book's display name, one +.B full +and one +.B abbr +form per id: +.RS +.nf + +luke.full = Evangelium secundum Lucam +luke.abbr = Luc +.fi +.RE +.PP +.B \-\-sigla\-book +selects which of the two forms +.RB ( "full" " or " "abbr" ) +a citation uses; the style's own +.B book +setting is the default when neither the flag nor the config key is given. +Unlike +.IR [celebration] " (deliberately partial for a new translation, per " NAMING +above), +.I [bible] +is expected complete: a missing entry degrades to the citation data's own +built\-in spelling (the pre\-naming form), never to another language's name +via the +.B fallback +chain \(em a book name silently borrowed from the wrong language would be +worse than one left untranslated. +.SS "lang/traditions.ini -- what a book denotes" +.I lang/traditions.ini +is a second file, separate from every language file, naming +.B traditions : +sections that remap a Vulgate\-numbered book id onto the id a modern reader +would expect. The shipped file: +.RS +.nf + +[vulgate] +; identity -- deliberately empty + +[modern] +kings_3 = kings_1 +kings_4 = kings_2 +esdras_2 = nehemiah +ecclesiasticus = sirach +osee = hosea +jonas = jonah +apocalypse = revelation +.fi +.RE +.PP +.B \-\-sigla\-tradition +names a section by its header; the default, +.BR vulgate , +is shipped deliberately empty, so a citation is never renumbered unless a +tradition is chosen explicitly \(em the 1962 Missal on which this engine's +data is built is Vulgate\-numbered throughout. +.B modern +renders the Vulgate id but with the OTHER tradition's name and numbering, so +.I "3 Kings 19:3\-8" +becomes +.IR "1 Kings 19:3\-8" , +via this mapping, not via a second copy of the reading data: +.RS +.nf + +$ colitur readings 2026 \-\-lang en | grep 2026\-02\-25 +2026\-02\-25 ef\-lent\-ember\-wed | 3 Kgs. 19:3\-8 | Matt 12:38\-50 | \e +Lenten Ember Wednesday +$ colitur readings 2026 \-\-lang en \-\-sigla\-tradition modern | grep 2026\-02\-25 +2026\-02\-25 ef\-lent\-ember\-wed | 1 Kgs 19:3\-8 | Matt 12:38\-50 | \e +Lenten Ember Wednesday +.fi +.RE +.PP +An unrecognised +.B \-\-sigla\-tradition +degrades to +.B vulgate +with a warning on standard error, never a hard error \(em asking for a +renumbering is optional, unlike asking for a language: +.RS +.nf + +colitur: .../lang/traditions.ini: no tradition "bogus"; falling back to the Vulgate +.fi +.RE +.SS "--raw is byte-exact" +.B \-\-raw +does not merely reformat a citation with an identity style \(em that would +still parse it and reprint its punctuation, which is not the same as +leaving it untouched. Under +.BR \-\-raw , +every citation is emitted +.B exactly +as stored, with no parsing step at all. Two reasons this matters, both +load\-bearing: +.RS +.nf + +1. diffing this program's output against lectio (the sibling Go engine + colitur's citation data is bootstrapped from) is only meaningful + byte\-for\-byte -- a reformatted citation would show spurious diffs + even where the two engines fully agree. +2. the raw view must not depend on the citation PARSER being correct -- + if a parser bug ever mis\-renders a citation, the raw output used to + diagnose that bug must not itself have gone through the same parser. +.fi +.RE +.SS Config keys +.BR sigla_style ", " sigla_book " and " sigla_tradition +in a config file's +.B [defaults] +section are the config\-file counterpart of +.BR \-\-sigla\-style ", " \-\-sigla\-book " and " \-\-sigla\-tradition , +resolved with the identical flag > config > default precedence as +.BR lang , +and reported the same way by +.BR "colitur config \-\-show" . +See +.B COMMANDS +above for each flag's own default and error behaviour, and +.BR colitur\-config (5) +for the config file's format, location and the four settings it currently +documents in full. +.SS Sourcing discipline +.IR lang/la.ini "'s own " +.I [bible] +rows are transcribed from the 1962 Missal's own reading incipits, each +citing a scan line \(em the same discipline +.I [celebration] +already follows. Two rows are marked where that was not straightforwardly +possible, so a reader can tell a sourced name from one that is not at a +glance rather than trusting silently: +.RS +.nf + +; UNSOURCED no instance of the book's own title was found in either + scan; the entry falls back to the citation data's own + built\-in spelling (e.g. Proverbs, Song of Songs). +; CONSTRUCTED composed from two separately\-sourced parts, because their + COMBINATION does not appear verbatim in the Missal (the + two Books of Kings: the shared incipit "Lectio libri + Regum" is sourced, the volume numeral comes from the + sourced chapter:verse locator, but no scan line spells + out "Liber Regum III" as such). +.fi +.RE +.PP +An unmarked row is transcribed verbatim (case aside). This is not an +apology for incompleteness \(em it is what lets a reader trust every +.I sourced +row precisely because the unsourced ones are labelled rather than blended +in silently. .SH ENVIRONMENT .TP .B COLITUR_DATA_DIR -- cgit v1.3 From 46ffa91fd4fcc2249cd097b3b3a639d37a7d592e Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 17:33:26 +0200 Subject: fix(lang): stop internal book ids reaching output, and finish the docs Three defects, all found by running the documented examples rather than by reading code. All seven modern-numbering targets carried their own internal id as their Latin name (kings_1.full = kings_1), so --sigla-tradition modern printed "kings_1 19:3-8" -- leaking a key book.mli states is never shown to a reader. They now carry Vulgate-derived Latin names marked CONSTRUCTED: the 1962 Missal uses Vulgate numbering throughout, so it can contain no incipit for a book that exists only under a later convention. In Latin the modern tradition therefore only moves Kings and Esdras; Osee, Ionas, Ecclesiasticus and the Apocalypse keep their names either way, since modern numbering is a vernacular convention. The coverage test could not catch this. It compares a name against its KEY ("kings_1.full"), and "kings_1" differs from that, so the row passed. A second check now asserts no name IS its own id. The two are complementary: one catches a missing row, the other a row present but filled with the wrong thing. Mutation-proved. --help still claimed sigla settings were resolved "but not yet rendered", stale since the render wiring landed. colitur-config.5 documented none of the three settings even though config --show prints them. --- bin/main.ml | 7 +++--- lang/la.ini | 51 ++++++++++++++++++++++++++----------- man/colitur-config.5 | 63 ++++++++++++++++++++++++++++++++++++++++++++++ test/test_lang_coverage.ml | 28 +++++++++++++++++++++ 4 files changed, 132 insertions(+), 17 deletions(-) (limited to 'man') diff --git a/bin/main.ml b/bin/main.ml index 5ad3132..c76a082 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1267,9 +1267,10 @@ naming: `abbr`), the same discipline as an unknown `--lang`; an unrecognised `--sigla-tradition` is not -- it degrades to the Vulgate with a stderr warning, because asking for a renumbering - is optional the way asking for a language is not. Settings - only, for now: this build resolves and reports them but does - not yet render a citation through them. + is optional the way asking for a language is not. `--raw` + bypasses all of it and emits each citation exactly as stored, + byte for byte, so it stays usable for diffing and does not + depend on the citation parser being correct. colitur lang --list which language files this build can find, and each one's own declared fallback, if any. diff --git a/lang/la.ini b/lang/la.ini index 369d6b3..3783482 100644 --- a/lang/la.ini +++ b/lang/la.ini @@ -2157,17 +2157,40 @@ apocalypse.abbr = Apoc ; render one of these bare ids straight into a citation. ; UNSOURCED (all seven rows below -- see this section's own header ; immediately above for why: a category mismatch, not a search gap). -kings_1.full = kings_1 -kings_1.abbr = kings_1 -kings_2.full = kings_2 -kings_2.abbr = kings_2 -nehemiah.full = nehemiah -nehemiah.abbr = nehemiah -sirach.full = sirach -sirach.abbr = sirach -hosea.full = hosea -hosea.abbr = hosea -jonah.full = jonah -jonah.abbr = jonah -revelation.full = revelation -revelation.abbr = revelation +; The seven MODERN-NUMBERING targets. All CONSTRUCTED, and necessarily so: +; the 1962 Missal uses Vulgate numbering throughout, so it can contain no +; incipit for a book that exists only under a later convention. Latin forms +; follow the Vulgate's own naming for the same physical book. +; +; They were previously set to the internal id ("kings_1.full = kings_1") by +; applying the UNSOURCED rule mechanically -- but that rule says to fall back +; to the spelling THE DATA USES, and these books are never cited by the data +; at all, so the fallback yielded the id. `--sigla-tradition modern` then +; printed "kings_1 19:3-8", leaking an internal key that book.mli states is +; never shown to a reader. +; +; Note that in LATIN the modern tradition only really moves Kings and Esdras: +; Osee, Ionas, Ecclesiasticus and the Apocalypse keep their Vulgate names, so +; those four map to an identical rendering. That is correct, not a no-op bug +; -- modern numbering is a vernacular convention, and these files show it. +kings_1.full = Liber Regum I +; CONSTRUCTED -- same reasoning as kings_3.full above, one volume lower. +kings_1.abbr = 1 Reg +kings_2.full = Liber Regum II +; CONSTRUCTED. +kings_2.abbr = 2 Reg +nehemiah.full = Liber Nehemiae +; CONSTRUCTED. The Vulgate calls this book 2 Esdras; see esdras_2 above. +nehemiah.abbr = Neh +sirach.full = Liber Ecclesiastici +; CONSTRUCTED. Same book as ecclesiasticus above, under its modern name. +sirach.abbr = Sir +hosea.full = Osee Propheta +; CONSTRUCTED. Identical to osee above -- see the note at the top of this block. +hosea.abbr = Os +jonah.full = Ionas Propheta +; CONSTRUCTED. Identical to jonas above. +jonah.abbr = Ion +revelation.full = Apocalypsis +; CONSTRUCTED. Identical to apocalypse above. +revelation.abbr = Apoc diff --git a/man/colitur-config.5 b/man/colitur-config.5 index 8f21028..6b3a574 100644 --- a/man/colitur-config.5 +++ b/man/colitur-config.5 @@ -181,6 +181,69 @@ accepts it. Relevant only to which requires a format either from this setting or from .BR \-\-format ; no default. +.TP +.B sigla_style +Which citation STYLE to render book names and punctuation with: a language +code, or a path to a file carrying a +.B [sigla] +section. Defaults to the resolved +.IR lang , +so a booklet's citations follow its own language unless told otherwise. +See +.BR colitur (1)'s +.B SIGLA +section for the five style settings. +.TP +.B sigla_book +.BR full " or " abbr . +Overrides the selected style's own +.B book +setting. Default +.BR abbr , +giving +.B "Luc. 5:12\-14" +rather than +.BR "Evangelium secundum Lucam 5:12\-14" . +An unrecognised value is a hard error, the same discipline as an unknown +.BR \-\-lang . +.TP +.B sigla_tradition +A section name in +.IR lang/traditions.ini , +deciding which book a reference DENOTES rather than what it is called. +Default +.BR vulgate , +the identity \(em colitur never renumbers unless asked. With +.B modern +the Vulgate's own +.B "3 Kings 19:3\-8" +renders as +.B "1 Reg 19:3\-8" +in Latin, or +.B "1 Kgs 19:3\-8" +under +.BR "\-\-lang en" . +In Latin the modern tradition only really moves Kings and Esdras: Osee, +Ionas, Ecclesiasticus and the Apocalypse keep their Vulgate names either +way, because modern numbering is a vernacular convention. +Unlike +.IR sigla_book , +an unrecognised value is NOT fatal: it warns on stderr and falls back to +.BR vulgate , +because asking for a renumbering is optional the way asking for a language +is not. +.PP +Naming and renumbering are deliberately separate mechanisms. +.I sigla_style +selects what a book is CALLED, which varies by language; +.I sigla_tradition +selects which book a reference DENOTES, which does not \(em "modern +numbering" is the same decision in Latin, Polish and English. Conflating +them is how a citation ends up naming the wrong book. +.PP +None of these affect +.BR \-\-raw , +which emits every citation exactly as stored, byte for byte. .SH UNKNOWN KEYS AND SECTIONS A key inside .B [defaults] diff --git a/test/test_lang_coverage.ml b/test/test_lang_coverage.ml index b2d32a4..0271a59 100644 --- a/test/test_lang_coverage.ml +++ b/test/test_lang_coverage.ml @@ -119,6 +119,32 @@ let test_every_book_named () = in Alcotest.(check (list string)) "every book named, both forms" [] missing +(* A name must not BE the internal id. The check above compares the value + against the KEY ("kings_1.full"), so a row reading `kings_1.full = kings_1` + passes it -- the two strings differ. That is exactly what shipped: all seven + tradition targets carried their own id as their name, and + `--sigla-tradition modern` printed "kings_1 19:3-8", leaking a key that + book.mli states is never shown to a reader. + + The two checks are complementary and neither subsumes the other: that one + catches a MISSING row, this one catches a row present but filled with the + wrong thing. *) +let test_no_book_name_is_an_internal_id () = + let la = la () in + let leaked = + List.concat_map + (fun id -> + let n = Colitur_citation.Book.to_string id in + List.filter_map + (fun form -> + let v = L.bible la (n ^ "." ^ form) in + if v = n then Some (n ^ "." ^ form) else None) + [ "full"; "abbr" ]) + Colitur_citation.Book.all + in + Alcotest.(check (list string)) "no book name is its own internal id" [] + leaked + (* lang/en.ini is DELIBERATELY partial (see its own header note): it declares [meta] fallback = la, so a slug it does not carry itself should still resolve through the chain to la.ini's name rather than degrade to the bare @@ -161,4 +187,6 @@ let suite = Alcotest.test_case "vocabularies complete" `Quick test_vocabularies_are_complete; Alcotest.test_case "every rite has a Latin name" `Quick test_every_rite_has_a_latin_name; Alcotest.test_case "every book named, both forms" `Quick test_every_book_named; + Alcotest.test_case "no book name is an internal id" `Quick + test_no_book_name_is_an_internal_id; Alcotest.test_case "en.ini falls back to Latin" `Quick test_en_falls_back_to_latin ] ) -- cgit v1.3 From 1da70dc7ac03fe33fb92b172a0e26932764170d6 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 19:03:02 +0200 Subject: fix(citation): close the final review's blocking findings The branch was RED and reported green. `dune test` exited 1: test/cli.t pinned the pre-fix output `kings_1 19:3-8`, which the previous commit had already fixed to `1 Reg 19:3-8`. The gate command piped dune through `tail`, so it reported tail's exit status, and cram prints its diff BEFORE the alcotest summary, so the two lines shown were the passing ones. Verify with `dune test; echo $?`, never through a pipe. A style file's own `book` key was unreachable. sigla_book resolved against a hardcoded "abbr" and the result was applied unconditionally, so the documented `[sigla] book = full` could never win. Render gains book_string, and the style's own value is now the default that a flag or config overrides. The unit test pinned style_of_fields correctly while the wiring defeated it. `lang --check` filtered the reference set to the celebration prefix, so a file with no [bible] section at all reported a clean bill of health -- contradicting both the reason the keys change was made and lang.ml's own comment. It now reports missing book names too. The token test missed a FOURTH citation-bearing file: adjustments.sexp writes citations as `Set_citation`, not `(reference ...)`. Its 16 citations all parse, so nothing was broken, but nothing was checking. The first attempt at this fix read the file and extracted NOTHING -- the marker stopped before the opening quote, so every payload was the part label -- which is recorded in the code rather than left as a trap. Also: colitur-config(5) claimed a trailing period the data does not carry, and two la.ini scan quotes silently corrected OCR damage ("Ionae 3, I - I O", "Epistolse") while presenting themselves as verbatim. Both are now marked as corrections. --- bin/main.ml | 31 ++++++++++++++++++++++++++++++- lang/la.ini | 13 +++++++++++-- lib/citation/render.ml | 1 + lib/citation/render.mli | 10 ++++++++++ man/colitur-config.5 | 2 +- test/cli.t | 23 +++++++++++++++-------- test/test_citation.ml | 32 ++++++++++++++++++++++++++------ 7 files changed, 94 insertions(+), 18 deletions(-) (limited to 'man') diff --git a/bin/main.ml b/bin/main.ml index c76a082..1207b1d 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -780,7 +780,11 @@ let load_sigla ~raw ~lang_t ~sigla_style_flag ~sigla_book_flag ~sigla_tradition_ in let sigla_book_value, _ = Colitur_naming.Config.resolve ~flag:sigla_book_flag - ~config:(Colitur_naming.Config.sigla_book config) ~default:"abbr" + ~config:(Colitur_naming.Config.sigla_book config) + (* The STYLE's own [book] key is the default, so a style file can + set it and a flag/config still overrides. A hardcoded "abbr" + here made the documented [sigla] book key unreachable. *) + ~default:(Colitur_citation.Render.book_string style) in let book_form = match sigla_book_value with @@ -1870,11 +1874,36 @@ let lang_check path = in let missing = List.filter (fun s -> not (List.mem s have)) known in let unknown = List.filter (fun s -> not (List.mem s known)) have in + (* Book names are checked the same way, and separately. Without + this the reference set gained a [bible] half that nothing ever + consulted: a file with [bible] entirely absent reported a clean + bill of health while every citation silently fell back to the + data's own Latin spelling. A miss is the TOTAL-lookup contract's + own signature -- [Lang.bible] returns the KEY when there is no + entry -- so equality with the key IS the test. *) + let missing_books = + List.concat_map + (fun id -> + let n = Colitur_citation.Book.to_string id in + List.filter_map + (fun form -> + let key = n ^ "." ^ form in + if Colitur_naming.Lang.bible t key = key then Some key + else None) + [ "full"; "abbr" ]) + Colitur_citation.Book.all + in List.iter (fun s -> Printf.printf "missing: %s\n" s) (List.sort compare missing); + List.iter (fun s -> Printf.printf "missing book: %s\n" s) + (List.sort compare missing_books); List.iter (fun s -> Printf.printf "unknown slug: %s\n" s) (List.sort compare unknown); + let books_total = 2 * List.length Colitur_citation.Book.all in Printf.printf "%s: %d of %d celebrations named, %d missing, %d unknown\n" path (List.length known - List.length missing) (List.length known) (List.length missing) (List.length unknown); + Printf.printf "%s: %d of %d book names, %d missing\n" path + (books_total - List.length missing_books) books_total + (List.length missing_books); if unknown <> [] then exit 1) (* `colitur config --show` -- each effective setting, its resolved value, diff --git a/lang/la.ini b/lang/la.ini index 3783482..821acff 100644 --- a/lang/la.ini +++ b/lang/la.ini @@ -1997,7 +1997,12 @@ jonas.full = Ionas Propheta ; scan1.txt:11021 "Lectio Ionae Prophetae." -- CORRECTS the sourcing note ; (genitive "Ionae" -> nominative "Ionas"). jonas.abbr = Ionae -; scan1.txt:11022 "Ionae 3, 1-10", same page as the .full citation -- the +; scan1.txt:11022 -- OCR-NORMALISED, not verbatim: the line actually reads +; "Ionae 3, I - I O", the scanner having read the digits 1 and 0 as capital +; letter I and letter O. The reading is unambiguous in context (Jonas 3 has +; 10 verses and the pericope below runs to verse 10), but the quote is a +; correction, not a transcription, and is marked so rather than presented as +; if the page said it. Same page as the .full citation -- the ; Missal's own locator uses the genitive form directly, unabbreviated. malachi.full = Malachias Propheta ; scan1.txt:26697 "Lectio Malachiae Prophetae." -- CORRECTS the sourcing @@ -2101,7 +2106,11 @@ hebrews.abbr = Hebr ; --- Catholic epistles -------------------------------------------------- james.full = Epistola beati Iacobi Apostoli -; scan1.txt:20348 "Lectio Epistolae beati Iacobi Apostoli.", also +; scan1.txt:20348 -- OCR-NORMALISED, not verbatim: the line reads +; "Epistolse beati Iacobi Apostoli", the scanner having read the ligature ae +; as "se". The correction is certain (no Latin word "Epistolse" exists, and +; the same phrase is clean elsewhere), but it is a correction and is marked +; as one. Also ; 20432/20509/34828/38918/40963/41197/45872/49193 -- no "ad X" destination ; to shorten to, so the attribution stays; see the header's own note on this ; pattern. diff --git a/lib/citation/render.ml b/lib/citation/render.ml index c66a0ca..79aaa86 100644 --- a/lib/citation/render.ml +++ b/lib/citation/render.ml @@ -20,6 +20,7 @@ let default_style = let part_sep st = st.part_sep let range st = st.range let book_sep st = st.book_sep +let book_string st = match st.book with `Full -> "full" | `Abbr -> "abbr" (* Arabic -> Roman, for the {chapter_roman} placeholder. Lifted from [Colitur_render.View.roman_numeral], which prints "Hebdomada I" week diff --git a/lib/citation/render.mli b/lib/citation/render.mli index d741931..274900c 100644 --- a/lib/citation/render.mli +++ b/lib/citation/render.mli @@ -44,5 +44,15 @@ val range : style -> string [\textasciitilde{}]. *) val book_sep : style -> string +(** The style's own book form, as the string a config file would write + (["full"] or ["abbr"]). + + Exists so a caller can use the STYLE's value as the default when + resolving the [sigla_book] setting. Resolving against a hardcoded + ["abbr"] instead makes a style file's own [book] key unreachable -- + the value is always overwritten before it can apply -- which is what + shipped until this accessor existed. *) +val book_string : style -> string + val render : style -> names:(Book.id -> [ `Full | `Abbr ] -> string) -> Parse.t -> string diff --git a/man/colitur-config.5 b/man/colitur-config.5 index 6b3a574..0e242bf 100644 --- a/man/colitur-config.5 +++ b/man/colitur-config.5 @@ -201,7 +201,7 @@ Overrides the selected style's own setting. Default .BR abbr , giving -.B "Luc. 5:12\-14" +.B "Luc 5:12\-14" rather than .BR "Evangelium secundum Lucam 5:12\-14" . An unrecognised value is a hard error, the same discipline as an unknown diff --git a/test/cli.t b/test/cli.t index d9d8ec5..b44a256 100644 --- a/test/cli.t +++ b/test/cli.t @@ -1261,12 +1261,14 @@ slug the engine can produce over 2020-2045: $ colitur lang --check d.ini d.ini: 725 of 725 celebrations named, 0 missing, 0 unknown + d.ini: 104 of 104 book names, 0 missing `--check` reports what is MISSING (a real slug with no entry): $ printf '[meta]\nlang = zz\n[celebration]\nef-epiphany = Test\n' > partial.ini - $ colitur lang --check partial.ini | tail -1 + $ colitur lang --check partial.ini | tail -2 partial.ini: 1 of 725 celebrations named, 724 missing, 0 unknown + partial.ini: 0 of 104 book names, 104 missing `--check` REJECTS an unknown slug (exit 1), so a typo is visible rather than silently dead -- its author would otherwise never learn why the name they @@ -1431,15 +1433,20 @@ Latin-convention punctuation. The synthetic file below overrides only `--sigla-tradition` renumbers which book an id DENOTES (lang/traditions.ini), independently of style or naming -- `modern` maps `3 Kings` onto the id -`kings_1`. Task 10's `la.ini` now carries a `[bible]` row for `kings_1` (a -tradition target the Vulgate data never cites directly, book.mli), but only -its own bare id -- an honest UNSOURCED placeholder, not a name (la.ini's own -`[bible]` header note) -- so it still prints literally, now BY DESIGN rather -than by the row's absence, still a real, if plain, witness that the -tradition actually applied rather than a no-op: +`kings_1`, whose own Latin name la.ini marks CONSTRUCTED: the 1962 Missal +uses Vulgate numbering throughout, so it can contain no incipit for a book +that exists only under a later convention. In Latin the modern tradition +therefore only really moves Kings and Esdras -- Osee, Ionas, Ecclesiasticus +and the Apocalypse keep their Vulgate names either way, because modern +numbering is a vernacular convention: $ colitur readings 2027 --sigla-tradition modern | grep '^2027-02-17' - 2027-02-17 ef-lent-ember-wed | kings_1 19:3-8 | Matth 12:38-50 | Feria IV Quatuor Temporum Quadragesimae + 2027-02-17 ef-lent-ember-wed | 1 Reg 19:3-8 | Matth 12:38-50 | Feria IV Quatuor Temporum Quadragesimae + +Under an English file the same mapping shows its usual face: + + $ colitur readings 2027 --lang en --sigla-tradition modern | grep '^2027-02-17' + 2027-02-17 ef-lent-ember-wed | 1 Kgs 19:3-8 | Matt 12:38-50 | Lenten Ember Wednesday `table`/`render`, `emit` and `publish` accept the same three flags too -- smoke-tested for exit status alone here (a minimal inline template, the diff --git a/test/test_citation.ml b/test/test_citation.ml index 1efaa10..133553b 100644 --- a/test/test_citation.ml +++ b/test/test_citation.ml @@ -117,8 +117,7 @@ let starts_with_at content pos prefix = [Str]/regex -- a plain forward scan for the marker, then read to the closing quote. Mirrors the coordinator's own survey command (grep -oh over the reference marker, quote-delimited). *) -let references content = - let marker = "(reference \"" in +let references_with marker content = let mlen = String.length marker in let len = String.length content in let rec loop pos acc = @@ -151,14 +150,35 @@ let book_token r = let stop = if !i < len && r.[!i] = '.' then !i + 1 else !i in String.sub r 0 stop +(* A citation is written TWO ways in this project's data, and a survey that + knows only one of them silently under-reads the corpus: + (reference "Isa 60:1-6") -- lectionary/sanctoral/commons + (Set_citation First "Wis 7:7-14") -- adjustments.sexp, an overlay + Both markers are scanned below. *) +let references content = + (* Each marker must include the OPENING QUOTE. [references_with] returns + the text between the marker and the next '"', so a marker stopping at + "(Set_citation " yields "First " / "Gospel " -- the part label, never + the citation -- and every entry is then silently discarded. That was + this function's first version, and it read the file while extracting + nothing at all. *) + references_with "(reference \"" content + @ references_with "(Set_citation First \"" content + @ references_with "(Set_citation Gospel \"" content + let test_every_data_file_token_resolves () = (* The check whose absence caused fix round 1: the brief surveyed only the lectionary and missed 21 tokens, several common, living in - sanctoral.sexp and commons.sexp. Read all three files at test time and - re-derive the token set from them, rather than hardcoding a list, so - this keeps working when the data changes. *) + sanctoral.sexp and commons.sexp. + + adjustments.sexp was then missed AGAIN, by this very test, because it + writes citations as `Set_citation` rather than `(reference ...)` -- the + same "one file too few" shape twice over. All FOUR shipped files are + read here, and the token set is re-derived from them at test time + rather than hardcoded, so this keeps working when the data changes. *) let files = - [ "../data/ef/lectionary.sexp"; "../data/ef/sanctoral.sexp"; "../data/ef/commons.sexp" ] + [ "../data/ef/lectionary.sexp"; "../data/ef/sanctoral.sexp"; + "../data/ef/commons.sexp"; "../data/ef/adjustments.sexp" ] in let tokens = files -- cgit v1.3