From 27606c42b7506ab7ffc7f1bd32d4d4a72c6400c8 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 20 Aug 2026 16:43:04 +0200 Subject: feat(render): citations render through Sigla at both call sites View.citation_ref and readings' part_ref are the only two places a citation reaches output; every emitter goes through View. --raw passes Sigla.verbatim rather than a style built over Lang.raw: an identity name table would still reformat punctuation and renumber. Loosen reject_sigla_for: --sigla-style/--sigla-book/--sigla-tradition now actually render on readings/table/render/emit/publish (each builds its own Sigla.t via the new load_sigla), and stay refused only on commands that render no citation (day, easter, temporal, ...). names_of degrades a Lang.bible miss to Book.default_spelling rather than printing the lookup key itself ("luke.abbr"); pinned in cli.t against a language file with no [bible] section at all, independent of la.ini/en.ini's own eventual [bible] section. Regenerate the 11 golden templates for 2027: every changed line is one canonical citation replacing a stored variant, verified against the full readings 2027 diff (raw vs default) cell by cell -- 86 distinct (raw, rendered) pairs account for the entire diff across all 11 files, with zero unexplained residue. Two families: the seven duplicate book spellings collapsing onto one canonical form (Isa./Isa, 3 Kgs./3 Kings, ...), and citations reconstructed from parsed structure dropping stray punctuation the parser already treats as noise (a trailing period or semicolon, a comma chapter/verse separator, an elided inherited chapter) -- each of the latter already named and tested in test_citation.ml's parse_suite before this task. --- bin/main.ml | 187 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 149 insertions(+), 38 deletions(-) (limited to 'bin') diff --git a/bin/main.ml b/bin/main.ml index 8434586..5ad3132 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -299,8 +299,14 @@ let day_line ~lang (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur can equally contain spaces, and " | " is what already keeps this row's fields unambiguous. Present only when the resolved name differs from the slug, for the identical reason [day_line] gives: under [--raw] this row - is therefore byte-identical to what it printed before. *) -let readings_line ~lang (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t) + is therefore byte-identical to what it printed before. + + [~sigla] (Task 9) is what [--raw]/[--sigla-style]/[--sigla-book]/ + [--sigla-tradition] actually reach: each stored reference renders + through {!Colitur_citation.Sigla.format} rather than printing verbatim. + Under {!Colitur_citation.Sigla.verbatim} (what [--raw] passes) this is + the identity, so the byte-exact claim above still holds. *) +let readings_line ~lang ~sigla (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t) = let cel = d.Colitur_kernel.Liturgical_day.observed in let part_ref p = @@ -309,7 +315,7 @@ let readings_line ~lang (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Co (fun (c : Colitur_kernel.Citation.t) -> c.Colitur_kernel.Citation.part = p) d.Colitur_kernel.Liturgical_day.citations with - | Some c -> c.Colitur_kernel.Citation.reference + | Some c -> Colitur_citation.Sigla.format sigla c.Colitur_kernel.Citation.reference | None -> "-" in let slug_s = Colitur_kernel.Slug.to_string cel.Colitur_kernel.Celebration.slug in @@ -414,7 +420,8 @@ let resolved_year_report ~line ~overlays y = List.iter line (resolved_year_days ~overlays y) let day_report ~lang ~overlays y = resolved_year_report ~line:(day_line ~lang) ~overlays y -let readings_report ~lang ~overlays y = resolved_year_report ~line:(readings_line ~lang) ~overlays y +let readings_report ~lang ~sigla ~overlays y = + resolved_year_report ~line:(readings_line ~lang ~sigla) ~overlays y (* Task 8: `colitur emit` -- the five template-family emitters built in Tasks 5-7, wired to a year RANGE rather than a single year, because a @@ -465,7 +472,7 @@ let check_dtstamp = function s; exit 2 -let emit_report ~lang ~format ~overlays ~dtstamp ~from_y ~to_y = +let emit_report ~lang ~sigla ~format ~overlays ~dtstamp ~from_y ~to_y = check_dtstamp dtstamp; if from_y > to_y then begin Printf.eprintf "colitur: --from %d is after --to %d\n" from_y to_y; @@ -474,7 +481,7 @@ let emit_report ~lang ~format ~overlays ~dtstamp ~from_y ~to_y = for y = from_y to to_y do let days = resolved_year_days ~overlays y in let v = - Colitur_render.View.of_days ~lang ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y days + Colitur_render.View.of_days ~lang ~sigla ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y days in match format with | "csv" -> @@ -675,11 +682,10 @@ let load_lang ~raw ~flag ~config = Called from [config_show] below (Task 8's own [--sigla-tradition] plumbing), which discards the returned [tradition] and keeps only the validation side effect -- `config --show` reports the RESOLVED STRING, - exactly like every other row, not a loaded object. Citations themselves - still reach output at exactly two places, [View.citation_ref] and - [part_ref] below, and threading the loaded [tradition] through both to - actually renumber a reference is Task 9's own dedicated job, not this - one's. *) + exactly like every other row, not a loaded object. Also called from + [load_sigla] below (Task 9), which DOES thread the loaded [tradition] + through to actually renumber a reference at both places one reaches + output, [View.citation_ref] and [part_ref]. *) let load_tradition name = let path = Filename.concat (lang_dir ()) "traditions.ini" in let vulgate () = Colitur_citation.Book.vulgate in @@ -704,6 +710,94 @@ let load_tradition name = (Colitur_citation.Book.unknown_fields sec.fields); Colitur_citation.Book.tradition_of_fields sec.fields)) +(* A book id's display NAME, for {!Colitur_citation.Sigla.make}'s own + [names] parameter. {!Colitur_naming.Lang.bible} is a TOTAL lookup that + returns THE KEY on a miss (["luke.abbr"]), because that is [Lang]'s own + miss contract, kept uniformly across every lookup it offers -- but a + caller that rendered a miss verbatim would print "luke.abbr 5:12-14" + straight into a citation. Degrade instead to + {!Colitur_citation.Book.default_spelling}, the data's own spelling for + the id -- exactly what colitur printed before this feature existed. + This path is invisible once [la.ini]/[en.ini] gain a real [\[bible\]] + section (Task 10); [test_cli.t] pins it against a language file that + deliberately has none, so the fallback stays under test after that. *) +let names_of lang id form = + let key = + Colitur_citation.Book.to_string id + ^ (match form with `Full -> ".full" | `Abbr -> ".abbr") + in + let v = Colitur_naming.Lang.bible lang key in + if v = key then Colitur_citation.Book.default_spelling id else v + +(* Builds the [Sigla.t] every citation-rendering command applies to what it + emits -- the single place [--raw]/[--sigla-style]/[--sigla-book]/ + [--sigla-tradition] actually take effect (Task 9), a sibling to + [load_lang] above and reusing it directly for [sigla_style]'s own file + lookup (config.mli: "a language CODE, looked up the same way [lang] is, + or a path"). + + [raw] short-circuits exactly as [load_lang] does: [Sigla.verbatim] is + handed back directly, NEVER a styled [Sigla.t] built over [Lang.raw] -- + the latter would still parse every citation and reformat its + punctuation, which is precisely what [--raw] exists to avoid (byte-exact + diffing against lectio, and independence from the parser: a parser bug + must not corrupt the very output used to diagnose it). Every other + [--sigla-*] flag is silently unconsulted under [--raw] too, the same way + [--lang] itself is once [--raw] is given. + + [sigla_style] DEFAULTS to [lang_t]'s own resolved code (config.mli's own + comment on [sigla_style]: a booklet that asked for a different [--lang] + gets its citations in that language's convention too, not a silent + reversion to Latin punctuation) -- when the resolved style code IS + [lang_t]'s own code, [lang_t] is reused directly rather than reading its + file a second time. Only the style file's OWN [\[sigla\]] section is + read here ({!Colitur_naming.Lang.sigla_fields} / + {!Colitur_citation.Render.style_of_fields}); book NAMES always resolve + through [lang_t] via [names_of] above, never through the style file -- + naming and citation style are deliberately independent axes (a booklet + may want Polish names but Latin-convention citations, config.mli again). + + [sigla_book] validation mirrors [config_show]'s own check exactly (a + closed [full]/[abbr] set, [Render.with_book] takes a variant, not a + string) -- unrecognised is a hard usage error, the same discipline an + unrecognised [--lang] gets. [sigla_tradition] resolves through + [load_tradition] above, which is BY DESIGN never fatal: asking for a + renumbering is optional, unlike asking for a language. *) +let load_sigla ~raw ~lang_t ~sigla_style_flag ~sigla_book_flag ~sigla_tradition_flag ~config = + if raw then Colitur_citation.Sigla.verbatim + else + let lang_code = Colitur_naming.Lang.code lang_t in + let style_code, _ = + Colitur_naming.Config.resolve ~flag:sigla_style_flag + ~config:(Colitur_naming.Config.sigla_style config) ~default:lang_code + in + let style_lang = + if style_code = lang_code then lang_t + else load_lang ~raw:false ~flag:(Some style_code) ~config:None + in + let style = + Colitur_citation.Render.style_of_fields (Colitur_naming.Lang.sigla_fields style_lang) + in + let sigla_book_value, _ = + Colitur_naming.Config.resolve ~flag:sigla_book_flag + ~config:(Colitur_naming.Config.sigla_book config) ~default:"abbr" + in + let book_form = + match sigla_book_value with + | "full" -> `Full + | "abbr" -> `Abbr + | _ -> + Printf.eprintf "colitur: unknown --sigla-book %S (want \"full\" or \"abbr\")\n" sigla_book_value; + exit 2 + in + let style = Colitur_citation.Render.with_book book_form style in + let sigla_tradition_value, _ = + Colitur_naming.Config.resolve ~flag:sigla_tradition_flag + ~config:(Colitur_naming.Config.sigla_tradition config) ~default:"vulgate" + in + let tradition = load_tradition sigla_tradition_value in + Colitur_citation.Sigla.make ~style ~tradition ~names:(names_of lang_t) + let extension path = match String.rindex_opt path '.' with | Some i -> String.sub path i (String.length path - i) @@ -734,7 +828,7 @@ let flavour_names_comma () = let flavour_names_bar () = String.concat "|" (List.map Colitur_render.Escape.to_string Colitur_render.Escape.all) -let table_report ~lang ~template ~flavour_opt ~overlays y = +let table_report ~lang ~sigla ~template ~flavour_opt ~overlays y = let flavour = match flavour_opt with | Some name -> ( @@ -757,7 +851,7 @@ let table_report ~lang ~template ~flavour_opt ~overlays y = exit 2 | Ok src -> ( let days = resolved_year_days ~overlays y in - let v = Colitur_render.View.of_days ~lang ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y days in + let v = Colitur_render.View.of_days ~lang ~sigla ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y days in match Colitur_render.Template.render_string ~flavour src v with | Error e -> (* The template is user input; a parse failure is reported with the @@ -944,7 +1038,7 @@ let index_html ~from_y ~to_y = \n"; Buffer.contents b -let publish_report ~lang ~from_y ~to_y ~out ~overlays ~dtstamp ~prune = +let publish_report ~lang ~sigla ~from_y ~to_y ~out ~overlays ~dtstamp ~prune = check_dtstamp dtstamp; if from_y > to_y then begin Printf.eprintf "colitur: --from %d is after --to %d\n" from_y to_y; @@ -976,7 +1070,7 @@ let publish_report ~lang ~from_y ~to_y ~out ~overlays ~dtstamp ~prune = in for y = from_y to to_y do let days = resolved_year_days ~overlays y in - let v = Colitur_render.View.of_days ~lang ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y days in + let v = Colitur_render.View.of_days ~lang ~sigla ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y days in let ys = string_of_int y in emit ("ef/" ^ ys ^ ".json") (Colitur_render.Emit_json.year v); emit ("ef/" ^ ys ^ ".csv") (Colitur_render.Emit_csv.year v); @@ -989,7 +1083,7 @@ let publish_report ~lang ~from_y ~to_y ~out ~overlays ~dtstamp ~prune = (fun d -> let iso = D.to_iso8601 d.Colitur_kernel.Liturgical_day.date in let mm = String.sub iso 5 2 and dd = String.sub iso 8 2 in - let one = Colitur_render.View.of_days ~lang ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y [ d ] in + let one = Colitur_render.View.of_days ~lang ~sigla ~vocab:Rite_ef.Vocab_ef.vocab ~rite:"ef" ~year:y [ d ] in emit (Printf.sprintf "ef/%s/%s/%s.json" ys mm dd) (Colitur_render.Emit_json.year one)) days done; @@ -1502,12 +1596,15 @@ let reject_lang_subcommand_flags_for cmd ~dump ~check ~list ~show = exit 2 end -(* Sibling again: `--sigla-style`/`--sigla-book`/`--sigla-tradition` (Task 8) - are settings plumbing only -- resolved and reported by `colitur config - --show` alone. Actually rendering a citation through them is Task 9's - job, so every OTHER command refuses the three flags rather than silently - accepting and ignoring them, the same discipline every rejector above - keeps; `config` is the one place that wires them in. *) +(* Sibling again: `--sigla-style`/`--sigla-book`/`--sigla-tradition` (Task 8 + plumbing, Task 9 wiring) actually render a citation on every command + that emits one -- `readings`, `table`/`render`, `emit`, `publish`, each + of which builds its own [Sigla.t] via [load_sigla] rather than calling + this rejector. Every OTHER command -- `day` included, which prints no + `first`/`gospel` field of its own -- still refuses the three flags + rather than silently accepting and ignoring them, the same discipline + every rejector above keeps; `config --show` is the one place that + previews their resolution without rendering anything. *) let reject_sigla_for cmd ~sigla_style ~sigla_book ~sigla_tradition = if sigla_style <> None || sigla_book <> None || sigla_tradition <> None then begin Printf.eprintf @@ -1799,16 +1896,16 @@ let lang_check path = - [sigla_book] is a closed two-value setting ([full]/[abbr]) -- [Colitur_citation.Render.with_book] takes a variant, not a string, so an unrecognised value could never mean anything downstream. Checked - HERE, not deferred to Task 9's actual renderer, because this is - currently the only place that consumes the value at all. + HERE too, not only inside [load_sigla]'s own real renderer (Task 9) -- + this preview must reject exactly what a real render would. - [sigla_tradition] names a section of [lang/traditions.ini]; resolving - it for real (via [load_tradition], the same reader Task 9's renderer - will use) rather than only printing the string means a typo is caught + it for real (via [load_tradition], the same reader [load_sigla] below + uses) rather than only printing the string means a typo is caught right here too -- though, unlike [sigla_book], [load_tradition] is BY DESIGN never fatal (see its own comment: asking for a renumbering is optional, unlike asking for a language), so an unknown tradition - degrades to a stderr warning and the Vulgate, exactly as it will under - Task 9, not a [config --show] failure. + degrades to a stderr warning and the Vulgate, exactly as it does on a + real render, not a [config --show] failure. [sigla_style] gets no such check: like [lang] itself, it is an open language code or path, not a closed set, and [config --show] does not validate [lang] either (that only happens when a command actually loads @@ -2073,13 +2170,16 @@ let () = reject_table "readings"; reject_publish "readings"; reject_lang_sub "readings"; - reject_sigla "readings"; - with_year ys (readings_report ~lang:(resolved_lang ()) ~overlays:effective_overlays) + let lang_t = resolved_lang () in + let sigla = + load_sigla ~raw ~lang_t ~sigla_style_flag:sigla_style ~sigla_book_flag:sigla_book + ~sigla_tradition_flag:sigla_tradition ~config + in + with_year ys (readings_report ~lang:lang_t ~sigla ~overlays:effective_overlays) | [ "emit" ] -> ( reject_table "emit"; reject_publish "emit"; reject_lang_sub "emit"; - reject_sigla "emit"; match (match format with Some f -> Some f | None -> Colitur_naming.Config.format config) with | None -> Printf.eprintf "colitur: emit requires --format csv|json|sexp|xml|ics\n"; @@ -2091,15 +2191,18 @@ let () = exit 2 | Some from_ys, Some to_ys -> let lang_t = resolved_lang () in + let sigla = + load_sigla ~raw ~lang_t ~sigla_style_flag:sigla_style ~sigla_book_flag:sigla_book + ~sigla_tradition_flag:sigla_tradition ~config + in with_year from_ys (fun from_y -> with_year to_ys (fun to_y -> - emit_report ~lang:lang_t ~format ~overlays:effective_overlays ~dtstamp ~from_y - ~to_y)))) + emit_report ~lang:lang_t ~sigla ~format ~overlays:effective_overlays ~dtstamp + ~from_y ~to_y)))) | [ ("table" | "render") as cmd ] -> ( reject_emit cmd; reject_publish cmd; reject_lang_sub cmd; - reject_sigla cmd; match (match template with Some t -> Some t | None -> Colitur_naming.Config.template config) with | None -> Printf.eprintf "colitur: %s requires --year YEAR and --template FILE\n" cmd; @@ -2111,14 +2214,18 @@ let () = exit 2 | Some ys -> let lang_t = resolved_lang () in + let sigla = + load_sigla ~raw ~lang_t ~sigla_style_flag:sigla_style ~sigla_book_flag:sigla_book + ~sigla_tradition_flag:sigla_tradition ~config + in with_year ys (fun y -> - table_report ~lang:lang_t ~template ~flavour_opt:flavour ~overlays:effective_overlays y) + table_report ~lang:lang_t ~sigla ~template ~flavour_opt:flavour + ~overlays:effective_overlays y) )) | [ "publish" ] -> ( reject_table "publish"; reject_format_for "publish" format; reject_lang_sub "publish"; - reject_sigla "publish"; match out with | None -> Printf.eprintf "colitur: publish requires --out DIR\n"; @@ -2130,6 +2237,10 @@ let () = exit 2 | Some from_ys, Some to_ys -> let lang_t = resolved_lang () in + let sigla = + load_sigla ~raw ~lang_t ~sigla_style_flag:sigla_style ~sigla_book_flag:sigla_book + ~sigla_tradition_flag:sigla_tradition ~config + in with_year from_ys (fun from_y -> with_year to_ys (fun to_y -> (* [publish_report] writes many files across a whole @@ -2148,8 +2259,8 @@ let () = underlying errno in [Sys_error] instead -- both are real on this path, so both are caught. *) try - publish_report ~lang:lang_t ~from_y ~to_y ~out ~overlays:effective_overlays - ~dtstamp ~prune + publish_report ~lang:lang_t ~sigla ~from_y ~to_y ~out + ~overlays:effective_overlays ~dtstamp ~prune with | Unix.Unix_error (e, fn, arg) -> Printf.eprintf "colitur: %s: %s: %s\n" fn arg (Unix.error_message e); -- cgit v1.3