summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bin/dune7
-rw-r--r--bin/main.ml74
-rw-r--r--lang/dune3
-rw-r--r--lang/traditions.ini21
-rw-r--r--test/dune6
-rw-r--r--test/test_citation.ml21
6 files changed, 126 insertions, 6 deletions
diff --git a/bin/dune b/bin/dune
index 28c04d7..0702a84 100644
--- a/bin/dune
+++ b/bin/dune
@@ -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 -> (
diff --git a/lang/dune b/lang/dune
index 057e95a..929dd42 100644
--- a/lang/dune
+++ b/lang/dune
@@ -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
diff --git a/test/dune b/test/dune
index a90b715..d883d2f 100644
--- a/test/dune
+++ b/test/dune
@@ -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