<feed xmlns='http://www.w3.org/2005/Atom'>
<title>colitur.git/bin/main.ml, branch main</title>
<subtitle>deterministic OCaml engine to compute and validate liturgical calendars for multiple rites, template-driven output to year 9999</subtitle>
<id>https://git.labunix.xyz/colitur.git/atom?h=main</id>
<link rel='self' href='https://git.labunix.xyz/colitur.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/'/>
<updated>2026-09-03T19:13:33Z</updated>
<entry>
<title>chore: 1.2.1</title>
<updated>2026-09-03T19:13:33Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-09-03T19:13:33Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=fe136f0952284b9a339252926dfd4675c4a7c3c6'/>
<id>urn:sha1:fe136f0952284b9a339252926dfd4675c4a7c3c6</id>
<content type='text'>
Ten wrong scripture references reached users; nothing else about the engine
changed and no output format moved. A patch bump.

README's status line and test count follow the release, as they have to: the
count is 917 under the exhaustive sweep, not 916.
</content>
</entry>
<entry>
<title>feat(cli): --help is one screen, not the whole manual</title>
<updated>2026-09-02T10:30:56Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-09-02T10:30:56Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=5dd14266f2609faadc3ec60077127a7ef7e8ac8b'/>
<id>urn:sha1:5dd14266f2609faadc3ec60077127a7ef7e8ac8b</id>
<content type='text'>
It printed 374 lines: every output format, the RFC 4180 quoting rule, the
template scope hazard, the publish manifest semantics. All of that is real
documentation and all of it is already in colitur(1), installed beside the
binary. A help text nobody reads at the terminal is not more helpful than a
short one, it is less -- the reader stops looking.

Now 42 lines: the commands, the flags most runs need, the year domain, the
exit status, and where the rest lives. Everything removed is in the man
pages, which is checked rather than assumed -- each dropped section has a
counterpart section there.

One thing moved rather than went. cli.t pinned `rubrics`' documented field
list against what `rubrics` actually prints, because that field count drifted
once already and stayed wrong for a whole review round. Deleting the prose
would have quietly retired the check, so it now reads the worked example out
of man/colitur.1 instead, and test/dune's cram stanza gains the man page as a
dep -- a sandbox holds only what that stanza names. Mutation-tested: changing
the documented example reddens the suite.

The tagline also stopped saying "Roman rite, 1962" -- the OF has shipped since
v1.1.0.
</content>
</entry>
<entry>
<title>chore: 1.2.0</title>
<updated>2026-08-28T08:03:32Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-28T08:03:32Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=8ba98d24499bedbc2491bd56268ea4f69aaddb2f'/>
<id>urn:sha1:8ba98d24499bedbc2491bd56268ea4f69aaddb2f</id>
<content type='text'>
Two presentation flags and no change to what is computed. Nothing was removed
and no default output moved, so a minor bump.

The CHANGELOG entry for 1.1.0 is backfilled here: that release was tagged
without one, which would have left this file jumping 1.0.0 -&gt; 1.2.0. Its
content is taken from the v1.1.0 tag message rather than reconstructed.

README's status section claimed the OF module lived on an unreleased branch
and counted 887 tests, and its build block advertised the fast suite at ~8s
and the exhaustive sweep at ~80s. Every one of those was true when written and
none is now: 909 fast (~40s), 916 under the sweep (~8min). Stale suite timings
are a defect this project has corrected once before, in 0.10.0.
</content>
</entry>
<entry>
<title>feat(cli): --month, --date and --today narrow a report</title>
<updated>2026-08-28T08:03:27Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-28T08:03:27Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=b39c8187ef1b87eea084e3c3d2226be5cf320dca'/>
<id>urn:sha1:b39c8187ef1b87eea084e3c3d2226be5cf320dca</id>
<content type='text'>
--pretty made a report readable and, in the same stroke, ungreppable: a box
spans seven lines, so `grep 2026-03-` returns fragments of March rather than
March. The nearest working equivalent is a paragraph-mode awk incantation that
depends on the boxes happening to be blank-line separated -- knowledge no
reader should need to select a month.

The three flags are ALTERNATIVES, not a stack. Naming two exits 2 rather than
letting one quietly win, the same discipline the positional year and --year
already follow, and the same reason this program refuses a flag it cannot
honour instead of accepting and ignoring it.

--date and --today CARRY a year, so the year becomes optional on those two and
required only after the window has had its say: `colitur day --today` is a
complete command while `colitur day` still is not. A year named alongside them
must agree. The disagreement message names the flag rather than printing two
bare numbers, because with --today the second year appears nowhere on the
command line for the reader to trace.

temporal refuses --year yet accepts --date/--today including as its year
source. That is not a back door to the refused spelling: --year is a second
spelling of the positional year, which temporal deliberately does not offer,
whereas --date selects a DAY and merely happens to determine which year
contains it.

The window is a predicate over an ISO date STRING rather than over a Date.t,
because the two report shapes reach it differently -- resolved days carry a
Date.t, temporal_report carries a flat Record whose date is already text --
and one shared test is one implementation rather than two that can drift.

Accepted by day, readings, rubrics and temporal; refused by easter, emit,
table, render, publish, check, convert, lang, config and new-overlay.

--pretty had shipped undocumented in both --help and colitur(1). Both now
carry it, alongside the new narrowing section; the man page's awk example is
shown precisely so the flag that replaces it is justified.

Default output is byte-identical to 1.1.0 across seven years, five commands
and both rites, and across all five emit formats and the shipped templates.
</content>
</entry>
<entry>
<title>feat(cli): --pretty draws each day as an ASCII box</title>
<updated>2026-08-27T13:45:49Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukasz@arcofasiagroup.com</email>
</author>
<published>2026-08-27T13:45:49Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=f1d562a3a4d53a707385334ab553b8a6cad36b1b'/>
<id>urn:sha1:f1d562a3a4d53a707385334ab553b8a6cad36b1b</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat(cli): --pretty, for reading in a terminal</title>
<updated>2026-08-27T12:12:43Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukasz@arcofasiagroup.com</email>
</author>
<published>2026-08-27T12:12:43Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=34a36fcb0956db7f06c4b8860414283c03996293'/>
<id>urn:sha1:34a36fcb0956db7f06c4b8860414283c03996293</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>chore: 1.1.0</title>
<updated>2026-08-27T11:05:58Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukasz@arcofasiagroup.com</email>
</author>
<published>2026-08-27T11:05:58Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=4b3c18d61a243f1ec9c39e6279f7c687402b8dda'/>
<id>urn:sha1:4b3c18d61a243f1ec9c39e6279f7c687402b8dda</id>
<content type='text'>
A second rite is new functionality that breaks nothing: EF output is
byte-identical to v1.0.0, no flag was removed, and emit --format csv's
seventeenth column appears only under --rite of. Minor bump, not major.
</content>
</entry>
<entry>
<title>fix(cli): make the flag surface systematic</title>
<updated>2026-08-27T10:33:43Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukasz@arcofasiagroup.com</email>
</author>
<published>2026-08-27T10:33:43Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=32e792a0741f62fc2c0c7dcf9255408b3256fad5'/>
<id>urn:sha1:32e792a0741f62fc2c0c7dcf9255408b3256fad5</id>
<content type='text'>
An audit probed every flag against every command rather than reading --help,
and found three inconsistencies.

--rite reached only `day` and `readings`. main.ml's own comment gave the
reason -- "has not been widened to a second rite in this task" -- a scope note
that had outlived its task and hardened into apparent design. View.of_days was
already polymorphic over the rite's type parameters, so widening was plumbing,
not library work: `temporal`, `rubrics`, `emit`, `table`, `render` and
`publish` now all take it.

`temporal` was the sharpest case. Its refusal said "--rite has no effect on
`temporal`", which was false: the EF has Septuagesima and Passiontide, the OF
neither, and every EF slug is ef-prefixed, so the flag would change nearly
every line. A message claiming no effect where the effect is total is exactly
what the audit set out to find.

Three ways of naming a year (positional, --year, --from/--to) now cross-accept
additively; naming two that disagree is a usage error rather than one silently
winning.

emit --format csv gained a rite-dependent header: EF's 16 columns are
unchanged, OF gets a 17th "second" between "first" and "gospel". An earlier
task had recorded RFC 4180 as permanently blocking this; the rule constrains
one file, not a family of them.

`rubrics` keeps refusing --lang/--raw, now with its reason. An intermediate
version accepted them by adding a name column, which changed the default from
six tab-separated fields to seven and broke both existing consumers and the
byte-identical-EF rule. The asymmetry is real but principled: the row is a
date, a slug, a source keyword, two booleans and a preface key, so there is
nothing to translate and nothing to strip. `easter` refuses --rite for the
same kind of reason -- its six anchors sit at identical Easter offsets in both
rites.

EF output verified byte-identical to 8590338 across day, readings, rubrics,
temporal and easter for 2026, 1583 and 9999, and across all five emit formats.
</content>
</entry>
<entry>
<title>fix(cli): render the Second reading on colitur readings</title>
<updated>2026-08-26T12:51:55Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T12:51:55Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=1c0137dee8ff3858707281366662891a5ff01044'/>
<id>urn:sha1:1c0137dee8ff3858707281366662891a5ff01044</id>
<content type='text'>
The OF lectionary (data/of/lectionary.sexp) carries 183 Second readings,
146 of them on Sunday keys, and the kernel resolves them correctly -- but
readings_line (EF) and readings_line_of (OF) in bin/main.ml only ever
printed Citation.First and Citation.Gospel, so `colitur readings --rite of`
silently dropped every Second reading it computed.

Both functions gain a second_suffix lookup, spliced between the existing
First and Gospel fields, present only when the day carries a Second
citation. No rite check: EF's citation_shapes is [[First; Gospel]] alone,
so EF rows are unaffected by construction, not by special-casing.

EF byte-identity verified two ways: `git diff --stat v1.0.0..HEAD --
data/ef/` is empty, and `colitur day`/`colitur readings --rite ef` for
2026, 1583 and 9999 cmp identical against a build of the prior commit.

test/cli.t: fixed the one now-stale assertion and added coverage for the
three-citation OF Sunday shape alongside the unchanged two-citation OF
weekday and EF shapes.
</content>
</entry>
<entry>
<title>fix(of): print the OF's own transcribed Latin/English names, not EF's</title>
<updated>2026-08-26T11:08:55Z</updated>
<author>
<name>Lukasz Kasprzak</name>
<email>lukas@labunix.xyz</email>
</author>
<published>2026-08-26T11:08:55Z</published>
<link rel='alternate' type='text/html' href='https://git.labunix.xyz/colitur.git/commit/?id=1a3046e2a3793fe52ec518818b19a6f15aadbb75'/>
<id>urn:sha1:1a3046e2a3793fe52ec518818b19a6f15aadbb75</id>
<content type='text'>
day_line_of/readings_line_of resolved every displayed name through
lang/la.ini -- data documented as "transcribed from the 1962 Missale
Romanum", i.e. EF vocabulary, keyed on slug. 18 of 222 shipped OF
sanctoral slugs collide with an EF slug there and all 18 printed the
WRONG (1962) title (e.g. "S. Marthae Virg.", a rank word the 1969
reform abolished, silently overriding amendment 011's own "Ss.
Marthae, Mariae et Lazari"); the other 204 had no entry and degraded
to a bare slug, so none of the 208 titles calendar-2002.sexp itself
transcribes (cited against the 2002 Missal) was ever reachable.

Add observed_name_of: prefer the observed celebration's own
Celebration.names (looked up by the requested language code), fall
back to the lang/*.ini table only on a miss. Under --raw this is a
no-op (Lang.raw's own code, "raw", is not a valid 2-letter ISO code,
so the names lookup always misses and falls through to the identity
table exactly as before). EF's day_line/readings_line are untouched --
EF's Celebration.names is almost always empty by design, lang/la.ini
is its real, independently-cited name table.

Regression net, two layers: test_lang_coverage.ml gains a test walking
the real assembled OF layer's entries, asserting that every genuine
lang/la.ini collision resolves to the OF's own name, never the EF
table's; test/cli.t pins one real collision (Martha, 29 July) through
the actual CLI in both la and en.
</content>
</entry>
</feed>
