diff options
| -rw-r--r-- | lib/citation/book.ml | 74 | ||||
| -rw-r--r-- | lib/citation/book.mli | 49 | ||||
| -rw-r--r-- | lib/citation/dune | 2 | ||||
| -rw-r--r-- | test/dune | 2 | ||||
| -rw-r--r-- | test/test_citation.ml | 43 | ||||
| -rw-r--r-- | test/test_colitur.ml | 1 |
6 files changed, 170 insertions, 1 deletions
diff --git a/lib/citation/book.ml b/lib/citation/book.ml new file mode 100644 index 0000000..a2f9678 --- /dev/null +++ b/lib/citation/book.ml @@ -0,0 +1,74 @@ +(* SPDX-License-Identifier: AGPL-3.0-or-later *) + +type id = string + +let to_string t = t + +(* Every book the shipped EF lectionary cites, with every spelling it uses. + The dotted/undotted pairs are inherited from lectio -- see book.mli. *) +let table = + [ ("genesis", [ "Gen" ]); + ("exodus", [ "Ex" ]); + ("leviticus", [ "Lev" ]); + ("numbers", [ "Num" ]); + ("kings_3", [ "3 Kings"; "3 Kgs." ]); + ("kings_4", [ "4 Kings" ]); + ("esdras_2", [ "2 Esd." ]); + ("esther", [ "Esther" ]); + ("ecclesiasticus", [ "Ecclus" ]); + ("isaiah", [ "Isa"; "Isa." ]); + ("jeremiah", [ "Jer" ]); + ("ezekiel", [ "Ezech" ]); + ("daniel", [ "Dan" ]); + ("osee", [ "Osee" ]); + ("joel", [ "Joel" ]); + ("jonas", [ "Jonas" ]); + ("matthew", [ "Matt"; "Matt." ]); + ("mark", [ "Mark" ]); + ("luke", [ "Luke" ]); + ("john", [ "John" ]); + ("acts", [ "Acts" ]); + ("romans", [ "Rom" ]); + ("corinthians_1", [ "1 Cor"; "1 Cor." ]); + ("corinthians_2", [ "2 Cor." ]); + ("galatians", [ "Gal" ]); + ("ephesians", [ "Eph"; "Eph." ]); + ("philippians", [ "Phil" ]); + ("colossians", [ "Col" ]); + ("thessalonians_1", [ "1 Thess"; "1 Thess." ]); + ("thessalonians_2", [ "2 Thess" ]); + ("titus", [ "Titus" ]); + ("hebrews", [ "Heb" ]); + ("james", [ "Jas" ]); + ("peter_1", [ "1 Pet"; "1 Pet." ]); + ("john_1", [ "1 John" ]) ] + +(* Targets a tradition can map ONTO that the Vulgate data never cites + directly. Present so [tradition_of_fields] can validate both sides. *) +let tradition_targets = + [ "kings_1"; "kings_2"; "nehemiah"; "sirach"; "hosea"; "jonah" ] + +let all = List.map fst table @ tradition_targets + +let tokens = + List.concat_map (fun (id, sp) -> List.map (fun s -> (s, id)) sp) table + +let of_token s = + let s = String.trim s in + List.assoc_opt s tokens + +type tradition = (string * string) list + +let vulgate = [] + +let known id = List.mem id all + +let tradition_of_fields fields = + List.filter (fun (a, b) -> known a && known b) fields + +let unknown_fields fields = + List.filter_map + (fun (a, b) -> if known a && known b then None else Some a) + fields + +let map tr id = match List.assoc_opt id tr with Some x -> x | None -> id diff --git a/lib/citation/book.mli b/lib/citation/book.mli new file mode 100644 index 0000000..dd04e0d --- /dev/null +++ b/lib/citation/book.mli @@ -0,0 +1,49 @@ +(* SPDX-License-Identifier: AGPL-3.0-or-later *) + +(** Bible books: one canonical id per book, and the tradition that decides + which book an id denotes. + + Ids follow the VULGATE structure ([kings_3], [esdras_2], + [ecclesiasticus]), because the Vulgate is the default tradition and the + shipped 1962 data is Vulgate throughout. An id is internal -- it is never + shown to a reader, exactly as a slug is never shown. *) + +type id + +val to_string : id -> string + +(** Resolve one spelling as it appears in the data. Returns [None] for + anything not in {!tokens}. + + SEVEN books arrive in two spellings ([Isa]/[Isa.], [3 Kgs.]/[3 Kings], + and five more). That inconsistency is INHERITED from lectio's own ini, + which is itself generated from missalemeum/Divinum Officium -- it is not + a colitur transcription error, and the data is deliberately left + untouched. Both spellings resolve here instead. *) +val of_token : string -> id option + +(** Every id this build knows, for coverage checks. *) +val all : id list + +(** Every accepted spelling paired with its id. *) +val tokens : (string * id) list + +(** A numbering tradition: which book an id denotes. Separate from NAMING + (what a book is called), which lives in a language file's [\[bible\]] + section, because naming varies by language and this does not -- "modern + numbering" is the same decision in Latin, Polish and English. *) +type tradition + +(** The identity tradition: the Vulgate, as the 1962 Missal prints it. The + default; colitur never silently renumbers. *) +val vulgate : tradition + +(** Build a tradition from an ini section's fields. An entry naming an + unknown id on either side is IGNORED, not fatal: a traditions file + written for a newer colitur must still work on an older one. Use + {!unknown_fields} to report them. *) +val tradition_of_fields : (string * string) list -> tradition + +val unknown_fields : (string * string) list -> string list + +val map : tradition -> id -> id diff --git a/lib/citation/dune b/lib/citation/dune new file mode 100644 index 0000000..e6cec3c --- /dev/null +++ b/lib/citation/dune @@ -0,0 +1,2 @@ +(library + (name colitur_citation)) @@ -1,6 +1,6 @@ (test (name test_colitur) - (libraries colitur_kernel colitur_naming colitur_render rite_ef alcotest qcheck qcheck-alcotest sexplib) + (libraries colitur_kernel colitur_naming colitur_render colitur_citation rite_ef alcotest qcheck qcheck-alcotest sexplib) (deps ../data/ef/sanctoral.sexp ../data/ef/adjustments.sexp diff --git a/test/test_citation.ml b/test/test_citation.ml new file mode 100644 index 0000000..0694d8b --- /dev/null +++ b/test/test_citation.ml @@ -0,0 +1,43 @@ +module B = Colitur_citation.Book + +let s = Alcotest.string + +let test_both_spellings_are_one_book () = + (* The seven inherited duplicate spellings must collapse. This is the + whole reason the parser exists rather than a regex. *) + let same a b = + match B.of_token a, B.of_token b with + | Some x, Some y -> + Alcotest.(check s) (a ^ " = " ^ b) (B.to_string x) (B.to_string y) + | _ -> Alcotest.failf "%s or %s did not resolve" a b + in + same "Isa" "Isa."; + same "Matt" "Matt."; + same "1 Cor" "1 Cor."; + same "1 Pet" "1 Pet."; + same "1 Thess" "1 Thess."; + same "Eph" "Eph."; + same "3 Kgs." "3 Kings" + +let test_unknown_token_is_none () = + Alcotest.(check bool) "not a book" true (B.of_token "Nonesuch" = None) + +let test_vulgate_is_identity () = + match B.of_token "3 Kings" with + | None -> Alcotest.fail "3 Kings did not resolve" + | Some k -> + Alcotest.(check s) "unmapped" "kings_3" (B.to_string (B.map B.vulgate k)) + +let test_modern_renumbers () = + let modern = B.tradition_of_fields [ ("kings_3", "kings_1") ] in + match B.of_token "3 Kings" with + | None -> Alcotest.fail "3 Kings did not resolve" + | Some k -> + Alcotest.(check s) "renumbered" "kings_1" (B.to_string (B.map modern k)) + +let suite = + ( "book", + [ Alcotest.test_case "both spellings one book" `Quick test_both_spellings_are_one_book; + Alcotest.test_case "unknown token" `Quick test_unknown_token_is_none; + Alcotest.test_case "vulgate identity" `Quick test_vulgate_is_identity; + Alcotest.test_case "modern renumbers" `Quick test_modern_renumbers ] ) diff --git a/test/test_colitur.ml b/test/test_colitur.ml index 1bdd220..46a31aa 100644 --- a/test/test_colitur.ml +++ b/test/test_colitur.ml @@ -4,6 +4,7 @@ let () = [ Test_date.suite; Test_computus.suite; Test_colour.suite; Test_slug.suite; Test_names.suite; Test_lang.suite; Test_lang_coverage.suite; + Test_citation.suite; Test_config.suite; Test_overlay.suite; Test_overlay_ini.suite; Test_temporal_ef.suite; Test_validate.suite; Test_precedence.suite; Test_calendar.suite; Test_precedence_ef.suite; Test_sanctoral_ef.suite; Test_rite_ef.suite; |
