aboutsummaryrefslogtreecommitdiff
path: root/test/test_ics.ml
Commit message (Collapse)AuthorAgeFilesLines
* fix(render): derive citation keys from the day, not a hardcoded pair (W5)Lukasz Kasprzak2026-08-271-0/+16
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | lib/render/view.ml hardcoded exactly two template keys, "first" and "gospel", regardless of what a day's citations actually contain. The OF now resolves a third reading, Second, on Sundays and solemnities (OLM 1981 Praenotanda n. 69.1/n. 84(b)(c)) via the rite-supplied Rite.t.citation_shapes, which the render layer could not express. Latent today (bin/main.ml's reject_rite_for refuses --rite on every command that reaches View.of_days, so only EF ever reaches this path), but the wrong shape to carry forward. View.citation_fields now derives one key per Citation.part ACTUALLY PRESENT in a day's own citations, in Citation.all_parts order, using Citation.part_to_string for the key name. An absent part contributes no key at all, not a key holding the empty string: Template.render already treats a missing key and a present Str "" identically in Var, Section and Inverted position, so nothing is lost, and Emit_json.year dumps the whole Obj verbatim, so an unconditionally-present-but-empty key for a still-unbuilt part (Psalm/Tract/Alleluia/Sequence) would sit in every EF day's JSON forever. Filtering to present parts alone is what makes EF's output identical byte for byte BY CONSTRUCTION rather than by a rite check: EF's citations are always exactly [First; Gospel], so the derived key set is always {"first"; "gospel"}, in that order. padding_cell keeps its own fixed "first"/"gospel" pair rather than deriving from a (nonexistent) day: it mirrors the closed set every rite currently reachable through this render path (EF alone) always carries, documented as debt for whenever a rite whose citation_shapes includes Second is admitted here -- test_padding_and_real_share_key_set would catch the mismatch immediately. Downstream consumers: Emit_json needed no change (already a verbatim dump). Emit_xml and Emit_ics gained a third, order-preserving element/line (a <citation part="second"> element; a "Second ..." DESCRIPTION line spliced between Epistle and Gospel) since neither is a fixed-column format. Emit_csv is deliberately left unwidened and commented: RFC 4180's header-row contract makes a variable column count illegal, and OF cannot reach `emit` today regardless, so there is nothing to widen for yet; admitting OF here later needs a disclosed, deliberate format change, not a silent one. term_keys (line ~239) is left unchanged, with a comment recording the judgment call: "epistle" and "lesson" are indeed EF/Roman vocabulary (Epistola vs. a non-apostolic Lectio, e.g. an Ember day's Old Testament reading) sitting in an otherwise rite-agnostic list, but nothing forces a fix yet -- no OF template exists, and every in-repo EF template already reads term.epistle, so renaming it would break 11 shipped templates and any user template for zero present benefit. "lesson" already sits unused, already translated in both lang files, and is the natural neutral term a future OF ordo template would reach for. Tests: test_view.ml gains test_ef_days_never_carry_a_second_key (a whole year, not one day) and test_three_citation_day_exposes_all_three, driving View.of_days directly with a hand-built Liturgical_day.t since OF cannot reach this path through the CLI yet -- the only honest way to exercise a latent path. test_emit.ml/test_ics.ml extend coverage to Emit_xml/Emit_ics (presence, value, and reading order) and pin Emit_csv's deliberate 16-column non-widening. EF byte-identity: zero diff in data/ef/ or lib/rites/rite_ef/ against the pre-change commit. `colitur day`, `readings`, `emit --format csv/xml/json/ ics`, and `table --template templates/ef/ordo.txt` for 2026, all non-empty, all byte-identical (cmp) against a worktree build of the pre-change commit.
* fix(render): omit DTEND at the domain's own last day, 9999-12-31Lukasz Kasprzak2026-08-191-1/+44
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | F1: Date.add_days is UNBOUNDED (date.mli) -- only Date.make enforces 1583..9999 -- and Date.to_iso8601 pads but never truncates, so 9999-12-31's naive successor formatted as "10000-01-01", and compact turned that into a 9-digit, non-conformant DATE on the last VEVENT of year 9999. Confirmed at the source before fixing, and reproduced against real `colitur emit --format ics --from 9999 --to 9999` output (DTEND;VALUE=DATE:100000101) before touching any code. RFC 5545 section 3.6.1: a VEVENT with a DATE-valued DTSTART and neither DTEND nor DURATION has an implicit one-day duration, so omitting DTEND for that one event is the standard's own correct answer, not a workaround. dtend_of re-derives the successor's year/month/day and re-validates them through Date.make -- the one function that actually enforces the domain -- before trusting the string; None means the caller omits the DTEND line entirely. F2 (minor, same function): documented next_day's own Error branch as dead-but-silent on shipped data (event's iso <> "" guard is the only caller and always parses) -- behaviour unchanged, comment only. Two new tests: the domain's last VEVENT (DTSTART 99991231) has no DTEND line at all; every DTEND anywhere in a 9999 feed is exactly 8 digits (the general form of the bug, catches a regression anywhere else in the domain too). Existing 2027/2028 DTEND-arithmetic assertions untouched and still pass. Mutation-proved: both new tests fail against the pre-fix code (9-digit DTEND value caught verbatim), pass after.
* feat(render): iCalendar emitter, RFC 5545Lukasz Kasprzak2026-08-191-0/+100
Not a template job: folding, escaping, exclusive DTEND and stable UIDs are rules a logic-less template cannot enforce, and each fails silently in a subscriber's client rather than loudly at generation. DTEND is EXCLUSIVE for an all-day event (section 3.6.1). Wrong here shows every event a day short, everywhere. UIDs are YYYYMMDD-<rite>@colitur and stable across regenerations (section 3.8.4.7). Wrong here duplicates the whole year in every subscriber's phone, months later. Every line is CRLF-terminated and folded at 75 octets (section 3.1). No RRULE: a liturgical calendar is not a recurrence rule. Asserted, so nobody optimises it later. DTSTAMP is a parameter, not a clock read. RFC 5545 requires it and the obvious implementation reads the wall clock -- which violates the kernel's determinism rule and would make two feeds from identical data differ byte-for-byte, defeating reproducible builds and any reviewable diff on a published tree. Corrected one test literal against real engine output: DTSTAMP is a per-VEVENT property (section 3.8.7.2), not calendar-level, so the default-value line count is 365 (every event), not 1. Mutation-tested: a non-exclusive DTEND reddens the suite.