aboutsummaryrefslogtreecommitdiff
path: root/bin/main.ml
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukasz@arcofasiagroup.com>2026-08-27 15:45:49 +0200
committerLukasz Kasprzak <lukasz@arcofasiagroup.com>2026-08-27 15:45:49 +0200
commitf1d562a3a4d53a707385334ab553b8a6cad36b1b (patch)
treef3bb14fbdd1ddb78aadb25168e8af17491ae953c /bin/main.ml
parent34a36fcb0956db7f06c4b8860414283c03996293 (diff)
downloadcolitur-f1d562a3a4d53a707385334ab553b8a6cad36b1b.tar.gz
colitur-f1d562a3a4d53a707385334ab553b8a6cad36b1b.zip
feat(cli): --pretty draws each day as an ASCII box
Aligned columns made the fields legible but the days ran together -- with 365 of them the eye had nothing to catch on. Each day now gets its own box: a heading carrying the date and the liturgical colour, then the celebration, its rank and season, and any commemorations, each on its own line. The box art is pure ASCII, only + - and |, never Unicode box-drawing. That is the point rather than a limitation: this format exists to be pasted or piped into a document, a mail or a plain-text ordo, and U+2500 and its relatives survive that only when every stage agrees about encoding and font. +---+ has never failed to render anywhere. readings gets a labelled block, so a citation says what it is instead of being the second of three bar-separated fields; the OF second reading simply omits its row on the days without one. rubrics becomes a label/value list. temporal is the day box minus the sanctoral it does not have. A blank line separates consecutive boxes -- without it the bottom rule of one day and the top rule of the next sit adjacent and read as a single doubled line, which is the same "not distinct enough" this change set out to fix. Alignment counts UTF-8 code points, not bytes, so "Pen~afort" and "Fremiot" still line the right edge up at 76 columns; a byte-counting pad shears the box by one per multi-byte character. Over-long values are truncated with a ~ rather than allowed to overflow, since a box whose right edge does not line up is worse than a clipped name that the default output still carries in full. Verified: no ANSI escape reaches a pipe on any of the four commands, and the default output is byte-identical to installed 1.1.0 across all five.
Diffstat (limited to 'bin/main.ml')
-rw-r--r--bin/main.ml145
1 files changed, 69 insertions, 76 deletions
diff --git a/bin/main.ml b/bin/main.ml
index 7c6427b..76c19e1 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -29,6 +29,33 @@ let easter_report y =
same claim. [Colitur_kernel.Record.of_temporal] was already fully
polymorphic over [('s, 'r)] (record.mli), so this is the same
plumbing already done for `day`/`readings`/etc, not new library work. *)
+let pretty_day_box ~date ~dow ~colour ~rank ~season ~week ~name ~comms ~extra =
+ let season_col =
+ match week with Some w -> Printf.sprintf "%s, week %s" season w | None -> season
+ in
+ print_endline (Pretty.rule ());
+ print_endline (Pretty.line_lr (date ^ " " ^ Pretty.cap dow) (Pretty.tint colour colour));
+ print_endline (Pretty.divider ());
+ List.iter (fun l -> print_endline (Pretty.line l)) (Pretty.wrap name);
+ print_endline (Pretty.line (rank ^ " . " ^ season_col));
+ (* Commemorations get their own rows inside the box rather than a suffix:
+ the EF admits up to three, and they are a different KIND of fact from the
+ day's own identity, which the box can show and a single row cannot. *)
+ List.iter (fun c ->
+ List.iter (fun l -> print_endline (Pretty.line l))
+ (Pretty.wrap (Pretty.dim "also: " ^ c)))
+ comms;
+ (match extra with
+ | [] -> ()
+ | rows ->
+ print_endline (Pretty.divider ());
+ List.iter print_endline rows);
+ print_endline (Pretty.rule ());
+ (* One blank line between boxes. Without it the bottom rule of one day and
+ the top rule of the next sit adjacent and read as a single doubled line,
+ which is exactly the "not distinct enough" this format exists to fix. *)
+ print_newline ()
+
let temporal_report ~rite ~pretty y =
let jan1 = match D.make ~year:y ~month:1 ~day:1 with
| Ok t -> t
@@ -55,22 +82,17 @@ let temporal_report ~rite ~pretty y =
while D.compare !d dec31 <= 0 do
let r = record_of_day !d in
(if pretty then
- (* The temporal cycle has no sanctoral, so there is never a
- commemoration to indent -- same row shape as `day --pretty`, minus
- that. Shown here from the flat Record rather than a Liturgical_day
- because that is all this report ever had. *)
- Printf.printf "%s %s %s%s %s%s\n"
- (Pretty.pad Pretty.w_date r.Colitur_kernel.Record.date)
- (Pretty.pad Pretty.w_dow
- (let w = r.Colitur_kernel.Record.weekday in
- String.sub w 0 (min 3 (String.length w))))
- (Pretty.swatch r.Colitur_kernel.Record.colour)
- (Printf.sprintf " %s" (Pretty.pad Pretty.w_rank r.Colitur_kernel.Record.rank))
- (Pretty.pad Pretty.w_season
- (match r.Colitur_kernel.Record.week with
- | "" -> r.Colitur_kernel.Record.season
- | w -> r.Colitur_kernel.Record.season ^ " " ^ w))
- r.Colitur_kernel.Record.slug
+ (* The temporal cycle carries no sanctoral, so a temporal box has no
+ commemorations and no proper name -- here the slug IS the identity.
+ Same box as `day --pretty`, one row shorter. *)
+ pretty_day_box ~extra:[] ~comms:[]
+ ~date:r.Colitur_kernel.Record.date
+ ~dow:r.Colitur_kernel.Record.weekday
+ ~colour:r.Colitur_kernel.Record.colour
+ ~rank:r.Colitur_kernel.Record.rank
+ ~season:r.Colitur_kernel.Record.season
+ ~week:(match r.Colitur_kernel.Record.week with "" -> None | w -> Some w)
+ ~name:r.Colitur_kernel.Record.slug
else
Printf.printf "%s %s %s %s %s %s %s\n" r.Colitur_kernel.Record.date
r.Colitur_kernel.Record.weekday r.Colitur_kernel.Record.season
@@ -791,43 +813,12 @@ let resolved_year_report ~line ~overlays y =
* see bin/pretty.ml's own note on why this format is free to change while
* every other one is a contract. *)
-let pretty_day_generic ~date ~dow ~colour ~rank ~season ~week ~name ~comms =
- let season_col =
- match week with
- | Some w -> Printf.sprintf "%s %s" season w
- | None -> season
- in
- (* The prefix is built, then MEASURED, rather than its width being assumed
- from the column constants. Those constants are a minimum, not a maximum
- -- a season name longer than its column (the Latin "Tempus per annum ante
- Septuagesimam" is 35 against 34) pushes everything after it right, and an
- indent computed from the constants would then sit under the wrong column
- on exactly the rows that have something to indent. *)
- let prefix =
- Printf.sprintf "%s %s " (Pretty.pad Pretty.w_date date)
- (Pretty.pad Pretty.w_dow (String.sub dow 0 (min 3 (String.length dow))))
- in
- let mid =
- Printf.sprintf " %s %s"
- (Pretty.pad Pretty.w_rank rank)
- (Pretty.pad Pretty.w_season season_col)
- in
- Printf.printf "%s%s%s%s\n" prefix (Pretty.swatch colour) mid name;
- (* Commemorations take their own indented line rather than a suffix: the EF
- admits up to three, and appending them runs the row past any sensible
- terminal width on precisely the days worth reading. The swatch counts as
- one display column however it was rendered. *)
- let indent = Pretty.utf8_len prefix + 1 + Pretty.utf8_len mid in
- List.iter
- (fun c -> Printf.printf "%s%s\n" (String.make indent ' ') (Pretty.dim ("+ " ^ c)))
- comms
-
let day_line_pretty ~lang (d : (Rite_ef.Vocab_ef.season, Rite_ef.Vocab_ef.rank) Colitur_kernel.Liturgical_day.t) =
let t = d.Colitur_kernel.Liturgical_day.temporal in
let cel = d.Colitur_kernel.Liturgical_day.observed in
let slug_s = Colitur_kernel.Slug.to_string cel.Colitur_kernel.Celebration.slug in
let name = Colitur_naming.Lang.celebration lang slug_s in
- pretty_day_generic
+ pretty_day_box ~extra:[]
~date:(D.to_iso8601 d.Colitur_kernel.Liturgical_day.date)
~dow:(D.weekday_to_string t.Colitur_kernel.Temporal.weekday)
~colour:(Colitur_kernel.Colour.to_string cel.Colitur_kernel.Celebration.colour)
@@ -847,24 +838,24 @@ let day_report ~lang ~pretty ~overlays y =
~line:(if pretty then day_line_pretty ~lang else day_line ~lang)
~overlays y
let readings_pretty_row ~date ~dow ~name ~first ~second ~gospel =
- Printf.printf "%s %s %s\n"
- (Pretty.pad Pretty.w_date date)
- (Pretty.pad Pretty.w_dow (String.sub dow 0 (min 3 (String.length dow))))
- name;
- let show label v =
- if v <> "" && v <> "-" then
- Printf.printf "%s%s %s\n"
- (String.make (Pretty.w_date + Pretty.w_dow + 5) ' ')
- (Pretty.dim (Pretty.pad 8 label)) v
+ print_endline (Pretty.rule ());
+ print_endline (Pretty.line (date ^ " " ^ Pretty.cap dow));
+ print_endline (Pretty.divider ());
+ List.iter (fun l -> print_endline (Pretty.line l)) (Pretty.wrap name);
+ let rows =
+ List.filter (fun (_, v) -> v <> "" && v <> "-")
+ [ ("First", first); ("Second", second); ("Gospel", gospel) ]
in
- (* Labelled and stacked rather than pipe-separated. A citation is what a
- person came here to read, and "Isai 63:16b-17, 19b; 64:2-7" is hard to
- find in a row of three when the separators are bars. The OF's second
- reading simply does not print on the days that have none, which is most
- of them. *)
- show "First" first;
- show "Second" second;
- show "Gospel" gospel
+ (* The label column is what makes a citation findable. In the default row
+ format the three references are separated by bars and you count fields to
+ tell which is which; here each says what it is. The OF second reading is
+ absent on most days and simply does not print a row. *)
+ if rows <> [] then begin
+ print_endline (Pretty.divider ());
+ List.iter (fun (k, v) -> print_endline (Pretty.line_kv k v)) rows
+ end;
+ print_endline (Pretty.rule ());
+ print_newline ()
let readings_line_pretty ~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
@@ -998,14 +989,14 @@ let day_line_of_pretty ~lang (d : (Rite_of.Vocab_of.season, Rite_of.Vocab_of.ran
Reusing [observed_name_of] rather than restating it keeps the two row
shapes from drifting apart. *)
let name = observed_name_of ~lang cel slug_s in
- pretty_day_generic
+ pretty_day_box ~extra:[] ~comms:[]
~date:(D.to_iso8601 d.Colitur_kernel.Liturgical_day.date)
~dow:(D.weekday_to_string t.Colitur_kernel.Temporal.weekday)
~colour:(Colitur_kernel.Colour.to_string cel.Colitur_kernel.Celebration.colour)
~rank:(Colitur_naming.Lang.rank lang (Rite_of.Vocab_of.rank_to_string cel.Colitur_kernel.Celebration.rank))
~season:(Colitur_naming.Lang.season lang (Rite_of.Vocab_of.season_to_string t.Colitur_kernel.Temporal.season))
~week:(match t.Colitur_kernel.Temporal.week with Some n -> Some (string_of_int n) | None -> None)
- ~name ~comms:[]
+ ~name
let day_report_of ~lang ~pretty ~overlays y =
resolved_of_year_report
@@ -1046,15 +1037,17 @@ let readings_report_of ~lang ~sigla ~pretty ~overlays y =
after the date differ, because what they are FOR differs. *)
let rubrics_pretty_row ~date ~said ~via ~creed ~gloria ~preface =
- let tick b = if b then "yes" else Pretty.dim "no" in
- Printf.printf "%s %s %s %s %s %s\n"
- (Pretty.pad Pretty.w_date date)
- (Pretty.pad 34 said)
- (Pretty.pad 10 via)
- (Pretty.pad 8 ("Creed " ^ tick creed))
- (Pretty.pad 9 ("Gloria " ^ tick gloria))
- preface
-
+ let yn b = if b then "yes" else "no" in
+ print_endline (Pretty.rule ());
+ print_endline (Pretty.line date);
+ print_endline (Pretty.divider ());
+ print_endline (Pretty.line_kv "Mass of" said);
+ print_endline (Pretty.line_kv "taken" via);
+ print_endline (Pretty.line_kv "Creed" (yn creed));
+ print_endline (Pretty.line_kv "Gloria" (yn gloria));
+ print_endline (Pretty.line_kv "Preface" preface);
+ print_endline (Pretty.rule ());
+ print_newline ()
let rubrics_line_pretty (d : (_, _) Colitur_kernel.Liturgical_day.t) =
let said, via =