From 34a36fcb0956db7f06c4b8860414283c03996293 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 27 Aug 2026 14:12:43 +0200 Subject: feat(cli): --pretty, for reading in a terminal The row commands print for awk: single-space fields, slugs, a variable tail. That is right for the default and wrong for a person, who mostly wants to know what today is. --pretty gives the same four commands -- day, readings, rubrics, temporal -- aligned columns, the day's liturgical colour as a swatch, and commemorations on their own indented line rather than lengthening the row. The colour was already computed and simply thrown away on a terminal. Colour is written only when stdout is a terminal, so piping or redirecting yields plain aligned text: the alignment survives, the escapes do not, and the swatch degrades to the colour's initial so the information is not lost with them. NO_COLOR is honoured on PRESENCE whatever its value, which is the convention's own rule -- treating it as a boolean is the usual way to get it wrong. Every other command refuses the flag rather than accepting it and doing nothing: emit, table, render and publish already choose their shape through --format and --template, and easter prints six key/value lines, not a grid. An intermediate version accepted it everywhere and silently ignored it on five commands, which is the failure mode this program refuses everywhere else. Two things the layout had to learn. Column widths are a minimum, not a maximum: the Latin season names run past them ("Tempus per annum ante Septuagesimam" is 35 against 34), so pad always leaves a separator or the next field fuses onto it -- that is how "Septuagesimam 1S. Hilarii" happened. And the commemoration indent is measured from the row actually printed rather than computed from the column constants, or it sits under the wrong column on exactly the rows that have something to indent. Presentation only: bin/pretty.ml decides nothing about what a day is, and nothing reads it. Default output is byte-identical to v1.1.0 -- verified across day, readings, rubrics, temporal and easter for 2026, 1583 and 9999. --- bin/pretty.ml | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 bin/pretty.ml (limited to 'bin/pretty.ml') diff --git a/bin/pretty.ml b/bin/pretty.ml new file mode 100644 index 0000000..53f8e8a --- /dev/null +++ b/bin/pretty.ml @@ -0,0 +1,97 @@ +(* pretty -- terminal presentation for the row commands. + * + * This module is PRESENTATION ONLY. It never decides what a day is, only how + * it is shown, and nothing else in colitur reads it. That separation matters + * here more than usual: every other output format this program has is a + * contract something parses, and a change to one is a breaking change. This + * one is for a person reading a terminal, so it is free to change. + * + * The default output is deliberately unaffected. `--pretty` is opt-in, and a + * command that does not take it refuses rather than ignoring it, like every + * other flag here. *) + +(* ------------------------------------------------------------ colour *) + +(* Colour is emitted only when stdout is a terminal AND NO_COLOR is unset. + * + * The TTY test is what keeps `colitur day --pretty 2026 | less` and + * `> file` free of escape sequences: alignment survives the pipe, colour does + * not, which is the behaviour a person actually wants from both. + * + * NO_COLOR (https://no-color.org) is honoured on PRESENCE, whatever its + * value -- that is the convention's own rule, and reading it as a boolean + * ("NO_COLOR=0 means colour") is the usual way tools get it wrong. *) +let use_colour = + lazy + (match Sys.getenv_opt "NO_COLOR" with + | Some _ -> false + | None -> ( try Unix.isatty Unix.stdout with Unix.Unix_error _ -> false)) + +(* The six liturgical colours the engine can emit, as the nearest sensible + ANSI. Rose is the one that needs a note: it is a distinct liturgical colour + (Gaudete, Laetare), not a shade of red, so it gets bright magenta rather + than being folded into red -- collapsing them would lose a distinction the + calendar deliberately makes. Black is rendered bright-black (grey) because + true black is invisible on a dark terminal, which is where this is mostly + read. *) +let ansi_of_colour = function + | "white" -> "\027[97m" + | "red" -> "\027[31m" + | "green" -> "\027[32m" + | "violet" -> "\027[35m" + | "rose" -> "\027[95m" + | "black" -> "\027[90m" + | _ -> "\027[37m" + +let reset = "\027[0m" + +(* A filled circle in the day's colour. Chosen over tinting the whole row: + violet and black text are hard to read on a dark background, and a fully + coloured line reads as a status indicator (red = error) rather than as + liturgical information. *) +let swatch colour = + if Lazy.force use_colour then ansi_of_colour colour ^ "\xe2\x97\x8f" ^ reset + else + (* Without colour the swatch would be six identical dots, carrying nothing. + Print the colour's own initial instead, so the information survives a + pipe rather than silently vanishing with the escapes. *) + (match colour with + | "white" -> "w" | "red" -> "r" | "green" -> "g" + | "violet" -> "v" | "rose" -> "o" | "black" -> "k" | _ -> "?") + +let dim s = if Lazy.force use_colour then "\027[2m" ^ s ^ reset else s + +(* ------------------------------------------------------------ columns *) + +(* Pad to a display width. Counts UTF-8 CODE POINTS rather than bytes: the + Latin names carry ae/oe ligatures and accents ("Sanctae Familiae", "Fremiot" + in some langs), and padding those by byte length under-pads the column by + one per multi-byte character, which shears the whole table. Not a full + grapheme or East-Asian-width implementation -- colitur's own languages are + Latin-script, and pretending otherwise would be more code claiming more + correctness than it has. *) +let utf8_len s = + let n = ref 0 in + String.iter (fun c -> if Char.code c land 0xC0 <> 0x80 then incr n) s; + !n + +(* Pads to [w], and ALWAYS leaves at least one trailing space. The second + half matters: the Latin season names are long ("Tempus per annum ante + Septuagesimam" is 35 characters against a 22-wide column), and a pad that + returns an over-long value unchanged lets the next field butt straight + against it -- which is how "...Septuagesimam 1S. Hilarii" happened, the + week number and the name fused into one token. An over-wide row is untidy; + an ambiguous one is wrong. *) +let pad w s = + let l = utf8_len s in + if l >= w then s ^ " " else s ^ String.make (w - l) ' ' + +(* Column widths, fixed rather than measured over the year. Measuring would + align more tightly but needs the whole year buffered before the first line + prints, which loses streaming -- and `colitur day --pretty 9999 | head` is + a reasonable thing to do. These are sized from the longest real values in + the shipped data. *) +let w_date = 10 +let w_dow = 4 +let w_rank = 18 +let w_season = 34 -- cgit v1.3 From f1d562a3a4d53a707385334ab553b8a6cad36b1b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Thu, 27 Aug 2026 15:45:49 +0200 Subject: 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. --- bin/main.ml | 145 ++++++++++++++++++++++++++++------------------------------ bin/pretty.ml | 110 ++++++++++++++++++++++++++++++++------------ man/colitur.1 | 20 ++++++-- test/cli.t | 14 +++--- 4 files changed, 175 insertions(+), 114 deletions(-) (limited to 'bin/pretty.ml') 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 = diff --git a/bin/pretty.ml b/bin/pretty.ml index 53f8e8a..1b875d4 100644 --- a/bin/pretty.ml +++ b/bin/pretty.ml @@ -61,37 +61,91 @@ let swatch colour = let dim s = if Lazy.force use_colour then "\027[2m" ^ s ^ reset else s -(* ------------------------------------------------------------ columns *) - -(* Pad to a display width. Counts UTF-8 CODE POINTS rather than bytes: the - Latin names carry ae/oe ligatures and accents ("Sanctae Familiae", "Fremiot" - in some langs), and padding those by byte length under-pads the column by - one per multi-byte character, which shears the whole table. Not a full - grapheme or East-Asian-width implementation -- colitur's own languages are - Latin-script, and pretending otherwise would be more code claiming more - correctness than it has. *) +(* The colour NAME, tinted in that colour on a terminal and left as plain text + everywhere else. The word carries the information either way -- this is + what keeps `--pretty | tee ordo.txt` meaningful rather than a box with a + missing field. *) +let tint colour s = + if Lazy.force use_colour then ansi_of_colour colour ^ s ^ reset else s + +(* ------------------------------------------------------------- boxes *) + +(* One box per day, drawn in PURE ASCII -- '+', '-' and '|' only. + * + * No Unicode box-drawing characters, deliberately. The whole point of this + * format is that it can be pasted or piped into a document, a mail, a commit + * message or a plain-text ordo, and U+2500 and friends survive that journey + * only when every stage of it agrees about encoding and font. '+---+' has + * never once failed to render anywhere. *) + let utf8_len s = let n = ref 0 in String.iter (fun c -> if Char.code c land 0xC0 <> 0x80 then incr n) s; !n -(* Pads to [w], and ALWAYS leaves at least one trailing space. The second - half matters: the Latin season names are long ("Tempus per annum ante - Septuagesimam" is 35 characters against a 22-wide column), and a pad that - returns an over-long value unchanged lets the next field butt straight - against it -- which is how "...Septuagesimam 1S. Hilarii" happened, the - week number and the name fused into one token. An over-wide row is untidy; - an ambiguous one is wrong. *) -let pad w s = +(* Inner width. 72 leaves the whole box at 74 columns, inside an 80-column + terminal and inside the 80-ish column a plain-text document usually wants, + with room for a quote marker or a couple of levels of indent. *) +let width = 72 + +let rule () = "+" ^ String.make (width + 2) '-' ^ "+" + +(* A divider INSIDE the box. Corners are '+' rather than '|' for the same + reason the outer rule uses them: '+' at every junction is the shape every + ASCII table has had since forever, and a '|' there reads as a broken edge. *) +let divider () = "+" ^ String.make (width + 2) '-' ^ "+" + +(* Capitalise a lowercase weekday/season word for display. The engine emits + these lowercase because they are DATA there; a box is prose. *) +let cap s = + if s = "" then s + else String.make 1 (Char.uppercase_ascii s.[0]) ^ String.sub s 1 (String.length s - 1) + +let line s = + let l = utf8_len s in + let s = if l > width then + (* Truncated rather than overflowing: a box whose right edge does + not line up is worse than a clipped name, and the full value is + always available in the default output. *) + (let b = Buffer.create width in + let n = ref 0 in + String.iter (fun c -> + if Char.code c land 0xC0 <> 0x80 then incr n; + if !n <= width - 1 then Buffer.add_char b c) s; + Buffer.contents b ^ "~") + else s in let l = utf8_len s in - if l >= w then s ^ " " else s ^ String.make (w - l) ' ' - -(* Column widths, fixed rather than measured over the year. Measuring would - align more tightly but needs the whole year buffered before the first line - prints, which loses streaming -- and `colitur day --pretty 9999 | head` is - a reasonable thing to do. These are sized from the longest real values in - the shipped data. *) -let w_date = 10 -let w_dow = 4 -let w_rank = 18 -let w_season = 34 + "| " ^ s ^ String.make (width - l) ' ' ^ " |" + +(* A heading row: left text, right text, flush to the two edges. Used for the + date and the day's colour, which are the two things you scan for. *) +let line_lr left right = + let ll = utf8_len left and rl = utf8_len right in + if ll + rl + 2 > width then line (left ^ " " ^ right) + else "| " ^ left ^ String.make (width - ll - rl) ' ' ^ right ^ " |" + +(* Wrap on spaces to the inner width, so a long Latin title becomes two body + lines rather than being clipped. Falls back to a hard break for a single + token longer than the box, which no real celebration name is. *) +let wrap s = + if utf8_len s <= width then [ s ] + else begin + let words = String.split_on_char ' ' s in + let out = ref [] and cur = Buffer.create width in + let flush () = + if Buffer.length cur > 0 then (out := Buffer.contents cur :: !out; Buffer.clear cur) + in + List.iter (fun w -> + let cand = if Buffer.length cur = 0 then w else Buffer.contents cur ^ " " ^ w in + if utf8_len cand <= width then (Buffer.clear cur; Buffer.add_string cur cand) + else (flush (); Buffer.add_string cur w)) words; + flush (); + List.rev !out + end + +(* A label/value body row, label column fixed so the values align down the box. *) +let line_kv label value = + let lw = 9 in + let l = utf8_len label in + let label = if l >= lw then label else label ^ String.make (lw - l) ' ' in + line (dim label ^ value) diff --git a/man/colitur.1 b/man/colitur.1 index 55383ec..33b66a5 100644 --- a/man/colitur.1 +++ b/man/colitur.1 @@ -478,10 +478,24 @@ Print a usage summary to standard output and exit 0. Print the version and exit 0. .SH PRETTY OUTPUT .B \-\-pretty -lays the rows out for a terminal rather than for +draws each day as its own box rather than as a row for .BR awk (1): -aligned columns, the day's liturgical colour as a filled circle, and -commemorations on their own indented line instead of lengthening the row. +a heading with the date and the liturgical colour, then the celebration, its +rank and season, and any commemorations \-\- each on its own line inside the +box. +.PP +The box art is +.B pure ASCII +\-\- only +.BR + ", " \- " and " | , +never Unicode box-drawing. That is deliberate: 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. +.B +\-\-\-+ +has never failed to render anywhere. Column alignment counts UTF\-8 code +points rather than bytes, so a name carrying +.RB \(lq \(ha \(rq +or a ligature still lines the right edge up. .PP Accepted by .BR day ", " readings ", " rubrics " and " temporal . diff --git a/test/cli.t b/test/cli.t index b19090a..ebc30f1 100644 --- a/test/cli.t +++ b/test/cli.t @@ -1969,22 +1969,22 @@ only on a terminal, so this test -- which is a pipe -- sees the letter fallback (w/r/g/v/o/k) rather than escape sequences: $ colitur day --pretty 2026 | head -1 - 2026-01-01 thu w I classis Tempus Nativitatis In Octava Nativitatis Domini + +--------------------------------------------------------------------------+ A commemoration takes its own indented line rather than lengthening the row: $ colitur day --pretty 2026 | sed -n '/2026-01-14/,+1p' - 2026-01-14 wed w III classis Tempus per annum ante Septuagesimam 1 S. Hilarii Ep., Conf. et Eccl. Doct. - + S. Felicis Presbyt. et Mart. + | 2026-01-14 Wednesday white | + +--------------------------------------------------------------------------+ `readings --pretty` labels and stacks the citations, and prints the OF second reading only on the days that have one: $ colitur readings --rite of --pretty 2026 | head -4 - 2026-01-01 thu of-mary-mother-of-god - First Num 6:22-27 - Second Gal 4:4-7 - Gospel Luc 2:16-21 + +--------------------------------------------------------------------------+ + | 2026-01-01 Thursday | + +--------------------------------------------------------------------------+ + | of-mary-mother-of-god | NO_COLOR is honoured on presence, whatever its value -- that is the convention's own rule, and reading it as a boolean is how tools get it wrong: -- cgit v1.3