aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/render/dune2
-rw-r--r--lib/render/emit_csv.ml16
-rw-r--r--lib/render/emit_ics.ml12
-rw-r--r--lib/render/emit_xml.ml17
-rw-r--r--lib/render/view.ml121
-rw-r--r--lib/render/view.mli18
6 files changed, 124 insertions, 62 deletions
diff --git a/lib/render/dune b/lib/render/dune
index 7880188..548cf43 100644
--- a/lib/render/dune
+++ b/lib/render/dune
@@ -1,5 +1,5 @@
(library
(name colitur_render)
- (libraries colitur_kernel sexplib)
+ (libraries colitur_kernel colitur_naming sexplib)
(preprocess
(pps ppx_sexp_conv)))
diff --git a/lib/render/emit_csv.ml b/lib/render/emit_csv.ml
index b478265..415a684 100644
--- a/lib/render/emit_csv.ml
+++ b/lib/render/emit_csv.ml
@@ -16,11 +16,15 @@ let escape_field s =
let get v k = match v with T.Obj kvs -> List.assoc_opt k kvs | _ -> None
let s v k = match get v k with Some (T.Str x) -> x | _ -> ""
-let nested v a b = match get v a with Some inner -> s inner b | None -> ""
+(* [name] is now the single RESOLVED display string (view.ml), not a
+ 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. *)
let columns =
- [ "date"; "rite"; "season"; "week"; "slug"; "rank"; "colour"; "subject";
- "name_la"; "name_en"; "first"; "gospel"; "comms" ]
+ [ "date"; "rite"; "season"; "season_name"; "week"; "slug"; "name"; "weekday";
+ "rank"; "rank_name"; "colour"; "colour_name"; "subject"; "first"; "gospel"; "comms" ]
let row ~rite d =
let comms =
@@ -30,9 +34,9 @@ let row ~rite d =
in
String.concat ","
(List.map escape_field
- [ s d "iso"; rite; s d "season"; s d "week"; s d "slug"; s d "rank";
- s d "colour"; s d "subject"; nested d "name" "la"; nested d "name" "en";
- s d "first"; s d "gospel"; comms ])
+ [ s d "iso"; rite; s d "season"; s d "season_name"; s d "week"; s d "slug";
+ s d "name"; s d "weekday"; s d "rank"; s d "rank_name"; s d "colour";
+ s d "colour_name"; s d "subject"; s d "first"; s d "gospel"; comms ])
let year v =
let rite = s v "rite" in
diff --git a/lib/render/emit_ics.ml b/lib/render/emit_ics.ml
index 41efb95..4e06eae 100644
--- a/lib/render/emit_ics.ml
+++ b/lib/render/emit_ics.ml
@@ -55,14 +55,10 @@ let line b l = Buffer.add_string b (Escape.fold_ics l)
let event b ~rite ~dtstamp d =
let iso = s d "iso" in
if iso <> "" then begin
- let name =
- match get d "name" with
- | Some (T.Obj kvs) -> (
- match List.assoc_opt "en" kvs with
- | Some (T.Str x) when x <> "" -> x
- | _ -> ( match List.assoc_opt "la" kvs with Some (T.Str x) -> x | _ -> s d "slug"))
- | _ -> s d "slug"
- in
+ (* [name] is the view's own resolved display string (view.ml) -- no
+ further fallback needed here: under [Lang.raw] it already equals
+ [slug], which is what a miss used to require picking by hand. *)
+ let name = s d "name" in
let summary = Printf.sprintf "%s (%s, %s)" name (s d "rank") (s d "colour") in
let desc =
String.concat "\n"
diff --git a/lib/render/emit_xml.ml b/lib/render/emit_xml.ml
index 4e4b51a..3f1ca32 100644
--- a/lib/render/emit_xml.ml
+++ b/lib/render/emit_xml.ml
@@ -11,20 +11,19 @@ let el b name value =
let day b d =
Buffer.add_string b (" <day date=\"" ^ escape (s d "iso") ^ "\">\n");
el b "season" (s d "season");
+ el b "season_name" (s d "season_name");
el b "week" (s d "week");
el b "slug" (s d "slug");
+ (* The resolved display name. A single element, no [lang] attribute: the
+ view's own [name] is now one resolved string, not a lang-keyed object
+ (view.ml), so there is exactly one to emit rather than one per language. *)
+ el b "name" (s d "name");
+ el b "weekday" (s d "weekday");
el b "rank" (s d "rank");
+ el b "rank_name" (s d "rank_name");
el b "colour" (s d "colour");
+ el b "colour_name" (s d "colour_name");
el b "subject" (s d "subject");
- (match get d "name" with
- | Some (T.Obj kvs) ->
- List.iter
- (fun (lang, v) ->
- match v with
- | T.Str x -> Buffer.add_string b (" <name lang=\"" ^ escape lang ^ "\">" ^ escape x ^ "</name>\n")
- | _ -> ())
- kvs
- | _ -> ());
(match get d "comms" with
| Some (T.List l) ->
List.iter (fun c -> Buffer.add_string b (" <commemoration>" ^ escape (s c "slug") ^ "</commemoration>\n")) l
diff --git a/lib/render/view.ml b/lib/render/view.ml
index 733aa45..bbb8c7d 100644
--- a/lib/render/view.ml
+++ b/lib/render/view.ml
@@ -1,18 +1,10 @@
module K = Colitur_kernel
module T = Template
+module Lang = Colitur_naming.Lang
let str s = T.Str s
let bool b = T.Bool b
-let month_names =
- [| ("Ianuarius", "January"); ("Februarius", "February"); ("Martius", "March");
- ("Aprilis", "April"); ("Maius", "May"); ("Iunius", "June");
- ("Iulius", "July"); ("Augustus", "August"); ("September", "September");
- ("October", "October"); ("November", "November"); ("December", "December") |]
-
-let names_value (n : K.Names.t) =
- T.Obj (List.map (fun (l, s) -> (K.Lang.to_string l, str s)) (K.Names.to_list n))
-
let dow_int = function
| K.Date.Sun -> 0 | K.Date.Mon -> 1 | K.Date.Tue -> 2 | K.Date.Wed -> 3
| K.Date.Thu -> 4 | K.Date.Fri -> 5 | K.Date.Sat -> 6
@@ -24,55 +16,78 @@ let citation_ref cits part =
| Some c -> c.K.Citation.reference
| None -> ""
-let comm_value (c, priv) =
+(* 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
+ (the ordo booklet, Task 8) interpolate this as a plain [{{name}}] inside
+ [{{#comms}}], so it must be a string here too, not the kernel's own
+ lang-keyed [Celebration.names] object -- the same reasoning [day_value]'s
+ own [name] follows below, applied consistently rather than left as a
+ second, differently-shaped name field a template author would have to
+ remember. *)
+let comm_value ~lang (c, priv) =
+ let slug_s = K.Slug.to_string c.K.Celebration.slug in
T.Obj
- [ ("slug", str (K.Slug.to_string c.K.Celebration.slug));
- ("name", names_value c.K.Celebration.names);
+ [ ("slug", str slug_s);
+ ("name", str (Lang.celebration lang slug_s));
("privileged", bool (priv = K.Precedence.Privileged)) ]
(* A padding cell: present so a grid row always has seven entries, and flagged
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. *)
+ 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. *)
let padding_cell dow =
T.Obj
[ ("iso", str ""); ("dom", str ""); ("dow", str (string_of_int dow));
("in_month", bool false);
- ("season", str ""); ("week", str ""); ("slug", str "");
- ("name", T.Obj []); ("rank", str "");
- ("colour", str "");
+ ("season", str ""); ("season_name", str "");
+ ("week", str ""); ("slug", str "");
+ ("name", str ""); ("weekday", str "");
+ ("rank", str ""); ("rank_name", str "");
+ ("colour", str ""); ("colour_name", str "");
("is_white", bool false); ("is_red", bool false); ("is_green", bool false);
("is_violet", bool false); ("is_rose", bool false); ("is_black", bool false);
("subject", str ""); ("comms", T.List []);
("transferred_in", T.List []); ("transferred_out", T.List []);
("first", str ""); ("gospel", str ""); ("last", bool false) ]
-let day_value ~vocab (d : ('s, 'r) K.Liturgical_day.t) =
+let day_value ~lang ~vocab (d : ('s, 'r) K.Liturgical_day.t) =
let date = d.K.Liturgical_day.date in
let tmp = d.K.Liturgical_day.temporal in
let cel = d.K.Liturgical_day.observed in
let colour = cel.K.Celebration.colour in
let is c = bool (colour = c) in
+ let slug_s = K.Slug.to_string cel.K.Celebration.slug in
+ let rank_s = vocab.K.Vocab.rank_to_string cel.K.Celebration.rank in
+ 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));
("dom", str (string_of_int (K.Date.day date)));
("dow", str (string_of_int (dow_int (K.Date.weekday date))));
("in_month", bool true);
- ("season", str (vocab.K.Vocab.season_to_string tmp.K.Temporal.season));
+ ("season", str season_s);
+ ("season_name", str (Lang.season lang season_s));
("week", str (match tmp.K.Temporal.week with Some w -> string_of_int w | None -> ""));
- ("slug", str (K.Slug.to_string cel.K.Celebration.slug));
- ("name", names_value cel.K.Celebration.names);
- (* [rank] is the kernel's own class string ("class-1"); there is
- deliberately no separate localized rank label here -- the kernel
- has no per-language rank names to draw one from, and a field
- whose contents cannot honestly differ from [name] should not
- exist just to exist. Do not re-add one until the kernel can. *)
- ("rank", str (vocab.K.Vocab.rank_to_string cel.K.Celebration.rank));
- ("colour", str (K.Colour.to_string colour));
+ ("slug", str slug_s);
+ (* The resolved display name. A plain string, not a lang-keyed object:
+ a dotted {{name.la}} used to fall back WHOLESALE to the enclosing
+ month's own name.la and print "Ianuarius" on unnamed days. One
+ string removes that hazard entirely -- there is no dotted path left
+ for a partial match to climb out of. Under [Lang.raw] this equals
+ [slug] exactly (every lookup in the identity table echoes its key),
+ which is what makes [--raw] output byte-stable. *)
+ ("name", str (Lang.celebration lang slug_s));
+ ("weekday", str (Lang.weekday lang (dow_int (K.Date.weekday date))));
+ ("rank", str rank_s);
+ ("rank_name", str (Lang.rank lang rank_s));
+ ("colour", str colour_s);
+ ("colour_name", str (Lang.colour lang colour_s));
("is_white", is K.Colour.White); ("is_red", is K.Colour.Red);
("is_green", is K.Colour.Green); ("is_violet", is K.Colour.Violet);
("is_rose", is K.Colour.Rose); ("is_black", is K.Colour.Black);
("subject", str (K.Subject.to_string cel.K.Celebration.subject));
- ("comms", T.List (List.map comm_value d.K.Liturgical_day.commemorations));
+ ("comms", T.List (List.map (comm_value ~lang) d.K.Liturgical_day.commemorations));
( "transferred_in",
T.List
(match d.K.Liturgical_day.transferred_in with
@@ -102,7 +117,14 @@ let set_last cells =
(fun i c -> match c with T.Obj kvs -> T.Obj (("last", bool (i = 6)) :: List.remove_assoc "last" kvs) | v -> v)
cells
-let weeks_of_month ~first_dow day_values =
+(* [month_num]/[month_name] are carried onto every WEEK object because the
+ engine has no {{../}} parent-path syntax: a nested {{num}} inside a week
+ silently finds the WEEK's own number, never the month's, so a template
+ that needs the month (the ordo booklet, Task 8, whose weeks span a
+ {{#months}}{{#weeks}} nesting) has no other way to reach it. Shaping the
+ data here, rather than inventing template syntax, is the same call the
+ [last] flag above already made. *)
+let weeks_of_month ~first_dow ~month_num ~month_name day_values =
let lead = List.init first_dow (fun i -> padding_cell i) in
let cells = lead @ day_values in
let rec chunk acc = function
@@ -118,11 +140,35 @@ let weeks_of_month ~first_dow day_values =
chunk (week :: acc) tl
in
List.mapi
- (fun i w -> T.Obj [ ("num", str (string_of_int (i + 1))); ("days", T.List (set_last w)) ])
+ (fun i w ->
+ T.Obj
+ [ ("num", str (string_of_int (i + 1)));
+ ("month_num", str month_num);
+ ("month_name", str month_name);
+ ("days", T.List (set_last w)) ])
(chunk [] cells)
-let of_days ~vocab ~rite ~year days =
- let dvs = List.map (fun d -> (d, day_value ~vocab d)) days in
+(* The [term] vocabulary a template routes every fixed string through
+ ({{term.epistle}}, {{term.week}}, ...) so a translated booklet needs no
+ 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. *)
+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)
+
+(* A localised grid header row: {name; last} objects, not bare strings --
+ the engine rejects an empty tag path ({{.}}) as a parse error, so a
+ template walking this list needs a named field to interpolate. Always
+ Sunday-first (index 0), matching [padding_cell]'s own [dow] numbering and
+ every week this view builds. *)
+let weekday_headings lang =
+ T.List
+ (List.init 7 (fun i -> T.Obj [ ("name", str (Lang.weekday lang i)); ("last", bool (i = 6)) ]))
+
+let of_days ~lang ~vocab ~rite ~year days =
+ let dvs = List.map (fun d -> (d, day_value ~lang ~vocab d)) days in
let months =
List.init 12 (fun i ->
let m = i + 1 in
@@ -135,15 +181,18 @@ let of_days ~vocab ~rite ~year days =
| (d, _) :: _ -> dow_int (K.Date.weekday d.K.Liturgical_day.date)
| [] -> 0
in
- let la, en = month_names.(i) in
+ let month_num = string_of_int m in
+ let month_name = Lang.month lang m in
T.Obj
- [ ("num", str (string_of_int m));
- ("name", T.Obj [ ("la", str la); ("en", str en) ]);
+ [ ("num", str month_num);
+ ("name", str month_name);
("days", T.List day_values);
- ("weeks", T.List (weeks_of_month ~first_dow day_values)) ])
+ ("weeks", T.List (weeks_of_month ~first_dow ~month_num ~month_name day_values)) ])
in
T.Obj
[ ("rite", str rite);
("year", str (string_of_int year));
+ ("term", term_value lang);
+ ("weekday_headings", weekday_headings lang);
("months", T.List months);
("days", T.List (List.map snd dvs)) ]
diff --git a/lib/render/view.mli b/lib/render/view.mli
index fb48590..81bfc26 100644
--- a/lib/render/view.mli
+++ b/lib/render/view.mli
@@ -15,10 +15,24 @@
different colour expression. Exactly one of the six is true on every day. *)
val of_days :
+ lang:Colitur_naming.Lang.t ->
vocab:('s, 'r) Colitur_kernel.Vocab.t ->
rite:string ->
year:int ->
('s, 'r) Colitur_kernel.Liturgical_day.t list ->
Template.value
-(** [of_days ~vocab ~rite ~year days] where [days] is one civil year, 1 January
- to 31 December, in order. Pure and total. *)
+(** [of_days ~lang ~vocab ~rite ~year days] where [days] is one civil year,
+ 1 January to 31 December, in order. Pure and total.
+
+ [lang] resolves every display string -- a day's [name], its localised
+ [weekday]/[rank_name]/[colour_name]/[season_name], each month's [name],
+ and the top-level [term] vocabulary and [weekday_headings]. [name] is a
+ PLAIN STRING, not the old lang-keyed object: a dotted [{{name.la}}]
+ reference that misses falls back WHOLESALE to the enclosing scope (a
+ month's own [name.la]), which is how a printed booklet came to show
+ "Ianuarius" in place of a feast with no Latin name. A plain string has no
+ dotted path to fall back through, so that hazard is unrepresentable.
+ [slug] is untouched by [lang] -- it stays the stable machine key,
+ identical between any two calls that differ only in [lang]. Pass
+ {!Colitur_naming.Lang.raw} for the pre-naming behaviour, under which
+ [name] equals [slug] exactly (this is what CLI [--raw] uses). *)