summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-20 15:49:00 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-20 15:49:00 +0200
commitc02e77b73aad34fb5a672a5234a66d64f23b45ce (patch)
treebff3ae0751e8005bc4f5448cc2992c3bcad2d86b
parent987175105ead0ea78b960c0e638a81c6fd2384cf (diff)
downloadcolitur-c02e77b73aad34fb5a672a5234a66d64f23b45ce.tar.gz
colitur-c02e77b73aad34fb5a672a5234a66d64f23b45ce.zip
feat(lang): read [bible] and [sigla] sections
Lang reads a hardcoded list of section names and ignores anything else silently, so without this a [bible] section would appear to work and do nothing -- confirmed directly with a scratch executable: before this change, of_string on text containing a [bible] section parsed without error and Lang.keys came back empty, no trace of the section anywhere. Lang.bible keeps the total-lookup contract every other lookup in this module has: a miss returns the key itself, never the empty string. Lang.sigla_fields returns the [sigla] section's raw fields, values trimmed but still quoted, for Render.style_of_fields to unquote. [bible] joins the keys reference set so lang --check reports missing book names; [sigla] deliberately does not, being settings with working defaults rather than translatable names -- adding it would make --check demand five settings from every language file.
-rw-r--r--lib/naming/lang.ml24
-rw-r--r--lib/naming/lang.mli27
-rw-r--r--test/test_lang.ml48
3 files changed, 95 insertions, 4 deletions
diff --git a/lib/naming/lang.ml b/lib/naming/lang.ml
index 2301513..f0fd989 100644
--- a/lib/naming/lang.ml
+++ b/lib/naming/lang.ml
@@ -16,6 +16,8 @@ type t = {
colour : table;
term : table;
rite : table;
+ bible : table;
+ sigla : table;
chain : t option; (** consulted when this table misses *)
}
@@ -35,6 +37,14 @@ let rank t k = get t (fun x -> x.rank) k
let colour t k = get t (fun x -> x.colour) k
let term t k = get t (fun x -> x.term) k
let rite t k = get t (fun x -> x.rite) k
+let bible t k = get t (fun x -> x.bible) k
+
+(* Not routed through [get]/[chain]: [sigla] is a set of RENDER SETTINGS
+ (Render.style_of_fields), not a per-key translation lookup, and it has no
+ miss-returns-the-key contract to keep -- a caller with no [sigla] table at
+ all just gets []. Order does not matter to callers (Render.style_of_fields
+ reads them by name), so [SM.bindings] (alphabetical) is fine here. *)
+let sigla_fields t = SM.bindings t.sigla
let weekday_key = [| "sunday"; "monday"; "tuesday"; "wednesday"; "thursday"; "friday"; "saturday" |]
@@ -65,7 +75,8 @@ let fallback_code t = t.fallback_code
let raw =
{ code = "raw"; fallback_code = None; celebration = empty_table; weekday = empty_table;
month = empty_table; month_abbr = empty_table; season = empty_table; rank = empty_table;
- colour = empty_table; term = empty_table; rite = empty_table; chain = None }
+ colour = empty_table; term = empty_table; rite = empty_table; bible = empty_table;
+ sigla = empty_table; chain = None }
let with_fallback t base = { t with chain = Some base }
@@ -107,12 +118,21 @@ let of_string text =
colour = find "colour";
term = find "term";
rite = find "rite";
+ bible = find "bible";
+ sigla = find "sigla";
chain = None })
+(* [bible] joins this reference set: it is translatable book names, so a file
+ lacking them is genuinely incomplete and [lang --check] should say so.
+ [sigla] deliberately does NOT: it is five citation-style settings with
+ working defaults (Render.default_style), not names a translator owes --
+ adding it here would make [--check] demand five settings from every
+ language file that has never needed them. *)
let keys t =
let qualify prefix m = SM.bindings m |> List.map (fun (k, v) -> (prefix ^ "." ^ k, v)) in
List.concat
[ qualify "celebration" t.celebration; qualify "weekday" t.weekday;
qualify "month" t.month; qualify "month_abbr" t.month_abbr; qualify "season" t.season;
- qualify "rank" t.rank; qualify "colour" t.colour; qualify "term" t.term; qualify "rite" t.rite ]
+ qualify "rank" t.rank; qualify "colour" t.colour; qualify "term" t.term; qualify "rite" t.rite;
+ qualify "bible" t.bible ]
|> List.sort compare
diff --git a/lib/naming/lang.mli b/lib/naming/lang.mli
index 34a6d5d..3c62d26 100644
--- a/lib/naming/lang.mli
+++ b/lib/naming/lang.mli
@@ -55,6 +55,13 @@ val term : t -> string -> string
degradation an unnamed slug already gets, not a special case. *)
val rite : t -> string -> string
+(** A Bible book's name, from the [\[bible\]] section. The key is
+ [<book_id>.full] or [<book_id>.abbr] (e.g. ["luke.abbr"]). A miss returns
+ the key itself, the same TOTAL-lookup contract as every other function
+ here -- callers turn that into a real fallback via
+ {!Colitur_citation.Book.default_spelling}, not this module. *)
+val bible : t -> string -> string
+
(** [weekday t n], 0 = Sunday. Out-of-range [n] returns [string_of_int n]. *)
val weekday : t -> int -> string
@@ -69,5 +76,23 @@ val month : t -> int -> string
val month_abbr : t -> int -> string
(** Every (section-qualified key, value) pair, sorted. Used by [lang --dump] and
- [lang --check]. Keys are qualified as e.g. ["celebration.ef-epiphany"]. *)
+ [lang --check]. Keys are qualified as e.g. ["celebration.ef-epiphany"].
+
+ Includes [\[bible\]] (["bible.luke.abbr"]): book names are translatable
+ text, so a file missing them is genuinely incomplete and [lang --check]
+ should say so. Deliberately EXCLUDES [\[sigla\]]: those are five citation-
+ style settings with working defaults ({!Colitur_citation.Render.default_style}),
+ not names a translator owes -- listing them here would make [--check]
+ demand five settings from every language file. *)
val keys : t -> (string * string) list
+
+(** The [\[sigla\]] section's raw fields, for
+ {!Colitur_citation.Render.style_of_fields} to read. Values come back
+ TRIMMED (whitespace at both ends) but still QUOTED if the file quoted
+ them -- unquoting is [Render]'s job, not this module's, the same reason
+ {!Colitur_kernel.Overlay_ini} (which this parses through) never unquotes
+ either. An empty (or absent) [\[sigla\]] section returns [[]]; unlike
+ every lookup above, there is no per-key TOTAL contract to keep here --
+ this is a settings bag, not a translation table, and [Render] already
+ supplies its own defaults for whatever is missing. *)
+val sigla_fields : t -> (string * string) list
diff --git a/test/test_lang.ml b/test/test_lang.ml
index c9b7f1a..59bb9a0 100644
--- a/test/test_lang.ml
+++ b/test/test_lang.ml
@@ -104,6 +104,48 @@ let test_duplicate_key_within_section_last_wins () =
let t = ok (L.of_string "[meta]\nlang = la\n[celebration]\na = FIRST\na = SECOND\n") in
Alcotest.(check string) "later line's value wins" "SECOND" (L.celebration t "a")
+(* Task 6: [bible] and [sigla] used to be two more section names [of_string]
+ never looked for -- a tenth (or eleventh) section a file author writes was
+ silently ignored, not rejected. Verified directly against a checked-out
+ scratch executable before this change: [Lang.keys] on a table built from
+ text containing a [bible] section came back with zero entries, no error
+ either. *)
+let test_bible_section_is_read () =
+ let text = "[meta]\nlang = xx\n[bible]\nluke.abbr = Lc\nluke.full = Ewangelia\n" in
+ match L.of_string text with
+ | Error e -> Alcotest.failf "parse: %s" e
+ | Ok t ->
+ Alcotest.(check string) "abbr" "Lc" (L.bible t "luke.abbr");
+ Alcotest.(check string) "full" "Ewangelia" (L.bible t "luke.full");
+ (* the TOTAL contract: a miss returns the key *)
+ Alcotest.(check string) "miss" "mark.abbr" (L.bible t "mark.abbr")
+
+let test_sigla_section_is_read () =
+ let text = "[meta]\nlang = xx\n[sigla]\nbook = full\npart_sep = \"; \"\n" in
+ match L.of_string text with
+ | Error e -> Alcotest.failf "parse: %s" e
+ | Ok t ->
+ let f = L.sigla_fields t in
+ Alcotest.(check (option string)) "book" (Some "full") (List.assoc_opt "book" f);
+ (* the quotes survive Lang; Render.style_of_fields strips them *)
+ Alcotest.(check (option string)) "sep" (Some "\"; \"") (List.assoc_opt "part_sep" f)
+
+(* The deliberate asymmetry: [bible] joins [keys] (lang --check's reference
+ set, so a translation missing every book name is reported as incomplete);
+ [sigla] does not (it is five settings with working defaults, not names a
+ translator owes -- putting it in [keys] would make --check demand five
+ settings from every language file). Pinned so a later change here is a
+ deliberate one, not a drive-by. *)
+let test_check_demands_bible_not_sigla () =
+ let text = "[meta]\nlang = xx\n[bible]\nluke.abbr = Lc\n[sigla]\nbook = full\n" in
+ match L.of_string text with
+ | Error e -> Alcotest.failf "parse: %s" e
+ | Ok t ->
+ let ks = L.keys t in
+ Alcotest.(check bool) "bible in keys" true (List.mem_assoc "bible.luke.abbr" ks);
+ Alcotest.(check bool) "sigla not in keys" false
+ (List.exists (fun (k, _) -> String.length k > 6 && String.sub k 0 6 = "sigla.") ks)
+
let suite =
( "Lang",
[ Alcotest.test_case "meta" `Quick test_meta;
@@ -117,4 +159,8 @@ let suite =
Alcotest.test_case "duplicate key across sections: last wins" `Quick
test_duplicate_key_across_sections_last_wins;
Alcotest.test_case "duplicate key within section: last wins" `Quick
- test_duplicate_key_within_section_last_wins ] )
+ test_duplicate_key_within_section_last_wins;
+ Alcotest.test_case "bible section is read" `Quick test_bible_section_is_read;
+ Alcotest.test_case "sigla section is read" `Quick test_sigla_section_is_read;
+ Alcotest.test_case "check demands bible not sigla" `Quick
+ test_check_demands_bible_not_sigla ] )