From 9dfa264f2941f1ca38eff08b6157a94d332cffbd Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 16:01:39 +0200 Subject: feat(lang): traditions.ini, and its install rule Which book a reference denotes does not vary by language, so it lives in its own file rather than in la.ini beside [weekday]. Six mappings, not four: Osee/Jonas are the same question as 3 Kings in transliteration form. The install rule is added in the same commit deliberately -- lang/ once shipped without one, so the feature worked from the source tree and was broken once installed. bin/dune gains colitur_citation as a linked library, needed for the new loader; lang_list's directory scan now excludes traditions.ini, which is not a language file and does not parse as one. --- lang/traditions.ini | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lang/traditions.ini (limited to 'lang/traditions.ini') diff --git a/lang/traditions.ini b/lang/traditions.ini new file mode 100644 index 0000000..e2d53ab --- /dev/null +++ b/lang/traditions.ini @@ -0,0 +1,21 @@ +; lang/traditions.ini -- which book a reference DENOTES. +; +; Separate from a language file's [bible] section, which decides what a book +; is CALLED: naming varies by language, this does not. "Modern numbering" is +; the same decision in Latin, Polish and English. +; +; The DEFAULT is [vulgate], as the 1962 Missal prints it. colitur never +; renumbers unless a tradition is chosen explicitly. +; +; Ids follow the Vulgate structure, because that is what the shipped data is. + +[vulgate] +; identity -- deliberately empty + +[modern] +kings_3 = kings_1 +kings_4 = kings_2 +esdras_2 = nehemiah +ecclesiasticus = sirach +osee = hosea +jonas = jonah -- cgit v1.3 From 263d0d889073d214229aef9655d3db29540c0d12 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 16:05:34 +0200 Subject: fix(citation): complete the modern tradition, and test the shipped file Apocalypse -> Revelation was missing from [modern] while `revelation` was already a declared tradition target, so the target existed with nothing mapping onto it. Same Vulgate-to-modern shape as Ecclesiasticus -> Sirach. The injectivity test used to hand-copy the mapping table, which asserted things about the copy and nothing about the artifact: a mapping added to traditions.ini and forgotten in the test would have passed. It now reads lang/traditions.ini and additionally asserts that [vulgate] is empty -- an entry there would silently renumber the DEFAULT, the one thing this design promises never happens -- and that no declared target is left unreachable. Mutation-proved three ways: declaring an unused target, deleting a mapping, and adding an entry to [vulgate] each redden it, the first two naming the offending book. --- lang/traditions.ini | 1 + test/test_citation.ml | 44 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) (limited to 'lang/traditions.ini') diff --git a/lang/traditions.ini b/lang/traditions.ini index e2d53ab..46f6ee7 100644 --- a/lang/traditions.ini +++ b/lang/traditions.ini @@ -19,3 +19,4 @@ esdras_2 = nehemiah ecclesiasticus = sirach osee = hosea jonas = jonah +apocalypse = revelation diff --git a/test/test_citation.ml b/test/test_citation.ml index d9be86e..1efaa10 100644 --- a/test/test_citation.ml +++ b/test/test_citation.ml @@ -180,13 +180,49 @@ let test_every_data_file_token_resolves () = always a typo -- it would silently merge two books -- so this asserts the mapping is injective, not merely that every id is known. *) let test_modern_tradition_is_injective () = - let fields = - [ ("kings_3", "kings_1"); ("kings_4", "kings_2"); - ("esdras_2", "nehemiah"); ("ecclesiasticus", "sirach"); - ("osee", "hosea"); ("jonas", "jonah") ] + (* READ the shipped file. An earlier version of this test hand-copied the + table, which asserted things about the copy and nothing at all about the + artifact -- a mapping added to traditions.ini and forgotten here would + have passed. *) + let text = + let ic = open_in_bin "../lang/traditions.ini" in + let s = really_input_string ic (in_channel_length ic) in + close_in ic; s in + let sections = + match Colitur_kernel.Overlay_ini.parse_sections text with + | Ok ss -> ss + | Error e -> Alcotest.failf "lang/traditions.ini: %s" e + in + let section name = + List.filter_map + (fun (sc : Colitur_kernel.Overlay_ini.section) -> + if sc.Colitur_kernel.Overlay_ini.name = name then + Some sc.Colitur_kernel.Overlay_ini.fields + else None) + sections + |> List.concat + in + (* [vulgate] must be the identity: an entry there would silently renumber + the DEFAULT, which is the one thing this design promises never happens. *) + Alcotest.(check (list (pair string string))) "vulgate is identity" [] + (section "vulgate"); + let fields = section "modern" in + Alcotest.(check bool) "modern is not empty" true (fields <> []); Alcotest.(check (list string)) "all ids known" [] (B.unknown_fields fields); + (* every declared tradition target is actually reachable -- an unused target + is either a forgotten mapping or dead weight *) let targets = List.map snd fields in + (* [B.tokens] is (spelling, id) pairs, so membership of an ID is a lookup + over the VALUES, not [mem_assoc] over the keys. Getting that backwards + flags every ordinary book as an unused target. *) + let cited = List.map (fun (_, i) -> B.to_string i) B.tokens in + List.iter + (fun id -> + let n = B.to_string id in + if (not (List.mem n cited)) && not (List.mem n targets) then + Alcotest.failf "%s is a declared target no tradition maps onto" n) + B.all; let uniq = List.sort_uniq compare targets in Alcotest.(check int) "no two ids share a target" (List.length targets) (List.length uniq) -- cgit v1.3