aboutsummaryrefslogtreecommitdiff
path: root/lib/render
diff options
context:
space:
mode:
Diffstat (limited to 'lib/render')
-rw-r--r--lib/render/emit_csv.ml20
-rw-r--r--lib/render/emit_ics.ml6
-rw-r--r--lib/render/emit_xml.ml9
-rw-r--r--lib/render/view.ml88
4 files changed, 115 insertions, 8 deletions
diff --git a/lib/render/emit_csv.ml b/lib/render/emit_csv.ml
index 415a684..e04211f 100644
--- a/lib/render/emit_csv.ml
+++ b/lib/render/emit_csv.ml
@@ -21,7 +21,25 @@ let s v k = match get v k with Some (T.Str x) -> x | _ -> ""
lang-keyed object -- the [name_la]/[name_en] pair this replaces carried
the kernel's own [Celebration.names], a different, unlocalised source.
Machine formats still carry [slug] alongside it, so a script keeps the
- stable key and a human reads the name. *)
+ stable key and a human reads the name.
+
+ W5: deliberately NOT widened with a "second" column, unlike
+ [Emit_xml]/[Emit_ics] (each a repeated-element/free-text format that
+ grows for free). A CSV's header row is a CONTRACT with every row under
+ it (RFC 4180 sec. 2: "each field in the header... should contain the
+ same number of fields as the records"), so there is no way to add a
+ column only on the days that carry a [Second] reading -- a real
+ three-citation day would either need an ALWAYS-present, mostly-empty
+ "second" column (widening every EF row forever for a case EF can never
+ hit) or genuinely variable-width rows (not CSV). Neither is a change to
+ make speculatively: [bin/main.ml]'s [reject_rite_for] refuses [--rite]
+ on `emit` entirely for any rite but EF, so no [Second] reading can reach
+ this module today, and EF's own [citations] is always exactly
+ [[First; Gospel]] ({!Rite_ef}'s own [citation_shapes]) -- there is
+ nothing to widen FOR yet. When OF is admitted here, this is the one
+ emitter that needs a deliberate, disclosed breaking change (a new
+ column, a version bump, or a documented "third reading silently
+ dropped" trade-off), not a silent one. *)
let columns =
[ "date"; "rite"; "season"; "season_name"; "week"; "slug"; "name"; "weekday";
"rank"; "rank_name"; "colour"; "colour_name"; "subject"; "first"; "gospel"; "comms" ]
diff --git a/lib/render/emit_ics.ml b/lib/render/emit_ics.ml
index b212930..c47031f 100644
--- a/lib/render/emit_ics.ml
+++ b/lib/render/emit_ics.ml
@@ -69,10 +69,16 @@ let event b ~rite ~dtstamp d =
already has to choose the localised one deliberately. *)
let name = s d "name" in
let summary = Printf.sprintf "%s (%s, %s)" name (s d "rank_name") (s d "colour_name") in
+ (* W5: [second] is inserted BETWEEN [first] and [gospel], not appended
+ after -- reading order (OLM 1981 Praenotanda n. 69.1's own "prima
+ lectio... secunda lectio... Evangelium"), not declaration order.
+ Absent for every EF day, the same "" no-op [cite] in [Emit_xml.day]
+ relies on, so EF's DESCRIPTION line is unchanged BY CONSTRUCTION. *)
let desc =
String.concat "\n"
(List.filter (fun x -> x <> "")
[ (if s d "first" <> "" then "Epistle " ^ s d "first" else "");
+ (if s d "second" <> "" then "Second " ^ s d "second" else "");
(if s d "gospel" <> "" then "Gospel " ^ s d "gospel" else "") ])
in
line b "BEGIN:VEVENT";
diff --git a/lib/render/emit_xml.ml b/lib/render/emit_xml.ml
index 3f1ca32..f0849cd 100644
--- a/lib/render/emit_xml.ml
+++ b/lib/render/emit_xml.ml
@@ -32,7 +32,16 @@ let day b d =
if value <> "" then
Buffer.add_string b (" <citation part=\"" ^ name ^ "\">" ^ escape value ^ "</citation>\n")
in
+ (* W5: a THIRD element, not a hardcoded pair -- [<citation>] is a repeated
+ element, not a fixed column, so there is no structural reason to leave
+ [second] out the way [Emit_csv]'s own fixed header row genuinely must
+ (see that module's own comment). [cite]'s own [value <> ""] guard
+ already makes this a no-op for every EF day (view.ml's [day_value]
+ never sets a "second" key for EF at all, so [s d "second"] resolves to
+ "" through [get]'s own [None -> ""] fallback) -- EF's XML is therefore
+ unchanged BY CONSTRUCTION, not by a rite check here. *)
cite "first" (s d "first");
+ cite "second" (s d "second");
cite "gospel" (s d "gospel");
Buffer.add_string b " </day>\n"
diff --git a/lib/render/view.ml b/lib/render/view.ml
index 67dfa70..0b122a7 100644
--- a/lib/render/view.ml
+++ b/lib/render/view.ml
@@ -54,6 +54,43 @@ let citation_ref ~sigla cits part =
| Some c -> Sigla.format sigla c.K.Citation.reference
| None -> ""
+(* W5: the day's own reading citations, keyed by [Citation.part_to_string]
+ and restricted to the parts THIS DAY ACTUALLY CARRIES -- generalises what
+ used to be exactly two hardcoded fields ([first]/[gospel]). A day with
+ [[First; Second; Gospel]] (OF, a Sunday or solemnity, OLM 1981
+ Praenotanda n. 69.1/n. 84(b)(c)) now exposes all three keys; a day with
+ [[First; Gospel]] (OF ferias/feasts/memorials, n. 66.1; every EF day,
+ unconditionally, RG-cited lectionary) exposes exactly the two it always
+ did. [K.Citation.all_parts] order (not [cits]' own order) makes the
+ result deterministic regardless of how the rite built the list.
+
+ An ABSENT part contributes NO KEY at all, not a present key holding the
+ empty string -- deliberately, for two independent reasons, not one:
+ (1) [Template.render]'s own documented contract already makes the two
+ indistinguishable at every place a template can observe them ([Var],
+ [Section], [Inverted] -- [Template.truthy] treats [Str ""] and a lookup
+ miss identically), so nothing is lost expressively; (2) {!Emit_json.year}
+ dumps this whole [Obj] verbatim, with no field list of its own to filter
+ through (unlike [Emit_csv]/[Emit_xml]/[Emit_ics], each of which names its
+ fields explicitly). [Citation.all_parts] also lists [Psalm]/[Tract]/
+ [Alleluia]/[Sequence] -- deliberately unbuilt by every rite that exists
+ today (no source, no oracle, CLAUDE.md) -- and NO rite will ever populate
+ them, so a present-but-always-empty key for each would sit in every EF
+ day's JSON forever. Filtering to only the PRESENT parts is what makes EF's
+ output identical byte for byte across every emitter, [Emit_json] included,
+ BY CONSTRUCTION rather than by a rite check here: EF's [citations] is
+ always exactly [[First; Gospel]] ({!Rite_ef}'s own [citation_shapes],
+ {!Colitur_kernel.Validate}'s "citations" check), so the derived key set
+ below is always {"first"; "gospel"}, in that order -- letter-for-letter
+ what the two fields this replaces used to produce. *)
+let citation_fields ~sigla cits =
+ List.filter_map
+ (fun part ->
+ match citation_ref ~sigla cits part with
+ | "" -> None
+ | s -> Some (K.Citation.part_to_string part, str s))
+ K.Citation.all_parts
+
(* A commemoration's own name resolves through the SAME [lang.celebration]
table as the observed day's -- a commemoration's slug is drawn from the
identical sanctoral/temporal pool, not a second vocabulary. Templates
@@ -74,6 +111,18 @@ let comm_value ~lang (c, priv) =
so a template can render it blank. Every field a real day has is present and
empty, so a template never hits a missing key on a padding cell -- the same
key SET as [day_value], not merely the same shape by coincidence. *)
+(* [first]/[gospel] stay a fixed pair here, deliberately NOT derived through
+ [citation_fields] (there is no day, hence no [citations] list, to derive
+ from): they mirror the closed set every rite CURRENTLY REACHABLE through
+ this whole grid/booklet path (EF alone -- [bin/main.ml]'s
+ [reject_rite_for] refuses [--rite] on every command that calls
+ [of_days]) always carries. A day with a [Second] reading can only enter a
+ grid once a rite whose [citation_shapes] includes it is admitted to
+ these commands -- not built yet, tracked as debt rather than
+ pre-built, CLAUDE.md's own "one field short of the remedy" discipline --
+ at which point this list needs the matching key added, or
+ [test_padding_and_real_share_key_set] (test_view.ml) will catch the
+ mismatch immediately. *)
let padding_cell dow =
T.Obj
[ ("iso", str ""); ("dom", str ""); ("dow", str (string_of_int dow));
@@ -100,7 +149,7 @@ let day_value ~lang ~sigla ~vocab (d : ('s, 'r) K.Liturgical_day.t) =
let colour_s = K.Colour.to_string colour in
let season_s = vocab.K.Vocab.season_to_string tmp.K.Temporal.season in
T.Obj
- [ ("iso", str (K.Date.to_iso8601 date));
+ ([ ("iso", str (K.Date.to_iso8601 date));
("dom", str (string_of_int (K.Date.day date)));
("dow", str (string_of_int (dow_int (K.Date.weekday date))));
("in_month", bool true);
@@ -138,11 +187,10 @@ let day_value ~lang ~sigla ~vocab (d : ('s, 'r) K.Liturgical_day.t) =
T.Obj
[ ("slug", str (K.Slug.to_string c.K.Celebration.slug));
("to", str (K.Date.to_iso8601 dest)) ])
- d.K.Liturgical_day.transferred_out) );
- ("first", str (citation_ref ~sigla d.K.Liturgical_day.citations K.Citation.First));
- ("gospel", str (citation_ref ~sigla d.K.Liturgical_day.citations K.Citation.Gospel));
- (* overwritten per grid row by [set_last]; false in the flat [days] list *)
- ("last", bool false) ]
+ d.K.Liturgical_day.transferred_out) ) ]
+ @ citation_fields ~sigla d.K.Liturgical_day.citations
+ @ [ (* overwritten per grid row by [set_last]; false in the flat [days] list *)
+ ("last", bool false) ])
(* Bucket a month's day values into Sunday-started weeks of exactly seven cells,
padding both ends. This is the computation the template cannot do. *)
@@ -235,7 +283,33 @@ let weeks_of_month ~first_dow ~month_num ~month_name ~month_abbr day_values =
template edit. The key list is the vocabulary's own fixed, closed set
(lang/*.ini's own [term] section, Task 1/3) -- not open like [celebration],
so it is named here rather than invented a second time from a wildcard
- enumeration. *)
+ enumeration.
+
+ W5 judgment call: ["epistle"] (and, less obviously, ["lesson"] -- the
+ 1962 Missal's own word for a non-apostolic reading, e.g. an Ember day's
+ Old Testament reading, as opposed to "Epistola" proper; see
+ [tools/bootstrap_lectionary.ml]'s "last lesson before the Gospel"
+ comments) ARE genuinely EF/Roman vocabulary sitting in this otherwise
+ rite-agnostic list, the same category CLAUDE.md's "carried into Plan 4"
+ section already tracks for [validate.ml]/[Liturgical_day.transferred_in]/
+ [Precedence.privilege]/[Repose]. Left UNCHANGED here rather than made
+ rite-supplied or renamed, for a reason specific to this key rather than a
+ blanket "not worth it": nothing FORCES the fix yet.
+ [bin/main.ml]'s [reject_rite_for] refuses [--rite] on every command that
+ reaches [term_value] (`table`/`emit`/`render`/`publish`), so OF cannot
+ reach a template that reads [term.epistle] today, and no OF template
+ exists under [templates/] to need a different word (only [templates/ef/]
+ does, and every one of them already reads [term.epistle]). Renaming it
+ would be a breaking change to those 11 shipped templates AND to any
+ user's own template, for zero present behavioural difference -- exactly
+ the trade [Rite.t.citation_shapes]'s own doc comment warns a template-key
+ rename always is. ["lesson"] already sits unused by any shipped template,
+ is already translated in both [lang/en.ini] ("Lesson") and [lang/la.ini]
+ ("Lectio"), and is the natural neutral term a future OF ordo template
+ would reach for -- so the infrastructure an eventual fix needs already
+ exists; only the forcing function (OF actually reaching this command) is
+ missing. Recorded here as the next instance of the pattern rather than
+ fixed speculatively, matching CLAUDE.md item 8's own discipline. *)
let term_keys = [ "ordo"; "contents"; "epistle"; "lesson"; "gospel"; "commemoration"; "week" ]
let term_value lang = T.Obj (List.map (fun k -> (k, str (Lang.term lang k))) term_keys)