diff options
| -rw-r--r-- | bin/dune | 7 | ||||
| -rw-r--r-- | bin/main.ml | 74 | ||||
| -rw-r--r-- | lang/dune | 3 | ||||
| -rw-r--r-- | lang/traditions.ini | 21 | ||||
| -rw-r--r-- | test/dune | 6 | ||||
| -rw-r--r-- | test/test_citation.ml | 21 |
6 files changed, 126 insertions, 6 deletions
@@ -6,4 +6,9 @@ ; colitur.opam's frozen depends, only a new library this executable links ; against. Used by Task 12's [mkdir_p] (colitur publish, recursive ; directory creation) and nowhere else. - (libraries colitur_kernel rite_ef colitur_render colitur_naming unix)) + ; + ; [colitur_citation] backs [load_tradition]'s own reading of + ; lang/traditions.ini (which book a reference DENOTES, {!Colitur_citation.Book}) -- + ; a sibling library to [colitur_naming] (what a book is CALLED), not a + ; replacement for it. + (libraries colitur_kernel rite_ef colitur_render colitur_naming colitur_citation unix)) diff --git a/bin/main.ml b/bin/main.ml index cf6cb33..5c9f342 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -641,6 +641,67 @@ let load_lang ~raw ~flag ~config = | Error _ -> t | Ok base -> Colitur_naming.Lang.with_fallback t base)))) +(* lang/traditions.ini answers a different question from la.ini/en.ini's own + [\[bible\]] section: which book a reference DENOTES, not what it is + CALLED. Naming varies by language (a [\[bible\]] section per language + file); denoting does not -- "modern numbering" is the same decision in + Latin, Polish and English -- so it gets its own file, read once here + rather than duplicated per language. + + Reuses {!lang_dir} rather than adding a second probe: a tradition file + must resolve the same way a language file does, or an installed binary + could find one and not the other. Note carefully what that reuse implies + -- [lang_dir]'s own installed-vs-source-tree choice is gated on [la.ini] + existing, so it can legitimately return a directory that HAS [la.ini] but + NOT [traditions.ini] (an older install upgraded in place, from before + this file shipped). That case, like an unknown [name] naming no section + in the file, degrades to {!Colitur_citation.Book.vulgate} with a WARNING + on stderr -- never fatal. This deliberately does NOT mirror [load_lang]'s + own PRIMARY-file behaviour (a missing/unparsing la.ini is fatal, [exit 2]) + -- it mirrors [load_lang]'s own FALLBACK-chain behaviour just above, + which already degrades silently rather than taking a good language table + down over a defect in a file it merely NAMES. Asking for a renumbering is + optional the way asking for a language is not: a run should not be lost + over a typo in [--sigla-tradition] (wired in Task 8) the way it is lost + over a typo in [--lang]. + + [name = "vulgate"] takes no shortcut around the file: [\[vulgate\]] is + shipped as a deliberately empty section (see traditions.ini's own + comment), and an empty field list is exactly {!Colitur_citation.Book.vulgate} + ([[]]) via the same {!Colitur_citation.Book.tradition_of_fields} path + every other tradition uses -- one code path, not a special case for the + default. + + Not called from anywhere in this file yet: citations reach output at + exactly two places, [View.citation_ref] and [part_ref] below, and + threading [Sigla.t] (which this feeds) through both is a dedicated task + on its own, deliberately not done here alongside the loader. [-32] is + silenced for exactly that reason -- this is a complete, correct, + ready-to-call function with no caller yet, not dead code. *) +let[@warning "-32"] load_tradition name = + let path = Filename.concat (lang_dir ()) "traditions.ini" in + let vulgate () = Colitur_citation.Book.vulgate in + match read_file path with + | Error _ -> + Printf.eprintf "colitur: no %s; tradition %S falls back to the Vulgate\n" path name; + vulgate () + | Ok text -> ( + match Colitur_kernel.Overlay_ini.parse_sections text with + | Error e -> + Printf.eprintf "colitur: %s: %s; tradition %S falls back to the Vulgate\n" path e name; + vulgate () + | Ok sections -> ( + match List.find_opt (fun (s : Colitur_kernel.Overlay_ini.section) -> s.name = name) sections with + | None -> + Printf.eprintf "colitur: %s: no tradition %S; falling back to the Vulgate\n" path name; + vulgate () + | Some sec -> + List.iter + (fun k -> + Printf.eprintf "colitur: %s: [%s]: unknown book %S (ignored)\n" path name k) + (Colitur_citation.Book.unknown_fields sec.fields); + Colitur_citation.Book.tradition_of_fields sec.fields)) + let extension path = match String.rindex_opt path '.' with | Some i -> String.sub path i (String.length path - i) @@ -1514,7 +1575,16 @@ let convert_report path = [lang_dir ()], the same probe [load_lang] itself uses. Each is opened and parsed (not merely listed by filename) so a malformed file is flagged here rather than only failing later when someone actually tries to use - it. *) + it. + + [traditions.ini] lives in the same directory but is not a language file + -- it answers which book a reference DENOTES, not what it is CALLED (see + [load_tradition] above) -- and does not even parse as one ([Lang.of_string] + requires its own [\[meta\]] section, which traditions.ini has no reason to + carry). Without this exclusion it would list here as "(unreadable: ...)", + which is not a defect in the file, only a mismatch between what this scan + assumes every [.ini] in the directory is and what is actually shipped + there now. *) let lang_list () = let dir = lang_dir () in match Sys.readdir dir with @@ -1525,7 +1595,7 @@ let lang_list () = Array.sort compare files; Array.iter (fun f -> - if Filename.check_suffix f ".ini" then begin + if Filename.check_suffix f ".ini" && f <> "traditions.ini" then begin let code = Filename.remove_extension f in match read_file (Filename.concat dir f) with | Ok t -> ( @@ -14,4 +14,5 @@ (package colitur) (files (la.ini as lang/la.ini) - (en.ini as lang/en.ini))) + (en.ini as lang/en.ini) + (traditions.ini as 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 @@ -6,6 +6,7 @@ ../data/ef/adjustments.sexp ../lang/la.ini ../lang/en.ini + ../lang/traditions.ini ../data/ef/expected-divergences.sexp ../data/ef/expected-divergences-missalemeum.sexp ../data/ef/lectionary.sexp @@ -55,4 +56,7 @@ ; back to the workspace root's own default alias), so schema/day-v1.json ; needs its own entry here too, exactly like every other runtime file ; above. - ../schema/day-v1.json)) + ../schema/day-v1.json + ; Same trap, same fix: Task 8's --sigla-tradition flag makes the CLI read + ; this file, and a cram sandbox holds only what this stanza names. + ../lang/traditions.ini)) diff --git a/test/test_citation.ml b/test/test_citation.ml index 25fc260..d9be86e 100644 --- a/test/test_citation.ml +++ b/test/test_citation.ml @@ -174,6 +174,23 @@ let test_every_data_file_token_resolves () = Alcotest.failf "unresolved book tokens in shipped data: %s" (String.concat ", " unresolved) +(* lang/traditions.ini's own [modern] section, duplicated here on purpose: + the shipped file and this expectation are written twice and must agree, + so a typo in either is visible. Two ids mapping onto the SAME target is + 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") ] + in + Alcotest.(check (list string)) "all ids known" [] (B.unknown_fields fields); + let targets = List.map snd fields in + let uniq = List.sort_uniq compare targets in + Alcotest.(check int) "no two ids share a target" + (List.length targets) (List.length uniq) + let suite = ( "book", [ Alcotest.test_case "both spellings one book" `Quick test_both_spellings_are_one_book; @@ -185,7 +202,9 @@ let suite = Alcotest.test_case "tokens has no duplicate spelling" `Quick test_tokens_has_no_duplicate_spelling; Alcotest.test_case "every data file token resolves" `Quick - test_every_data_file_token_resolves ] ) + test_every_data_file_token_resolves; + Alcotest.test_case "modern tradition is injective" `Quick + test_modern_tradition_is_injective ] ) module P = Colitur_citation.Parse |
