Commit Graph
5 Commits
Author SHA1 Message Date
funkpower 83447db964 fix a crash, a false changelog entry, and three things 1.0 would freeze
Two adversarial reviews of the release. Both said do not ship, and they were
right twice over.

The seven-day view panicked. elide() cut a title at cut.len() / 2, a byte
index into a string it had counted in characters, and every movable feast
title carries an em dash: at fourteen terminal widths, 80 among them, it
aborted with exit 101 on the README's own headline command. tests/elide.rs now
cuts every title the program can print at every width it can be asked to print
at.

The changelog's first Calendar entry was false. It said a vigil or polyeleos
saint reaches fish in the three lesser fasts; the code gives wine and oil to
every rank but great, and caps the Dormition Fast at wine and oil whatever the
rank. A reader in the Dormition Fast was told this release let a saint bring
fish. docs/versioning.md makes that section a contract, so a wrong entry there
is worse than a wrong comment.

The Python bindings read the config file where the CLI reads the environment
first, so TYPIKON_JURISDICTION moved one and not the other and the two
answered for different churches on the same machine. CI now asserts they
agree, and runs the README example checker, which was standing between the
docs and reality with nothing running it.

Three changes to the surface, made now because after 1.0.0 they are a major
version: binds is null rather than the string none where a church legislates
no binding fast at all; iCalendar UIDs carry the tradition and the discipline,
which two feeds of the same day were sharing while saying different things;
and Date's Julian Day Number is behind an accessor rather than a public field.

fastLabel, emoji and reason are documented as presentation. The fields are
stable, the wording is not, and freezing three human-facing strings for a
major version was not the intent.
2026-09-18 13:46:57 -04:00
funkpower 7970f785c8 fix what a 1.0 would have welded into PyPI metadata
An adversarial review of the release, and four of its findings were things
PyPI never lets you replace once uploaded.

  - rust-version said 1.74. clap has needed 1.85 since 4.6, and cargo
    hard-errors rather than warning, so the sdist advertised a floor it could
    not build on. docs/releasing.md sends macOS and Windows users to that
    sdist.
  - The wheel was classified Development Status :: 4 - Beta on a release the
    changelog calls stable, with none of the Python version classifiers PyPI
    indexes on.
  - Six README links were relative. PyPI resolves them against the project
    page, so every one 404'd there, as they do on the live 0.2.0 page now.
  -  in that order packs the wheel into the
    sdist. The tarball on disk was 431KB with an x86-64 binary inside it.

Also: twine upload gains --skip-existing, so a retry after a partial upload
finishes rather than dying on a version that cannot be reused; twine check
gains the renderer that makes it parse the description at all, without which
it returns None and reports PASSED; and the README no longer claims a
twenty-year sweep the test did not run, because 366 * 21 / 4 is five years.
The test now runs the twenty-one it was named for.

The crates.io name belongs to an unrelated project, which the README now says
rather than leaving a reader to find out from cargo.
2026-09-18 13:41:41 -04:00
funkpower 3e3b585b2a a jurisdiction is not just a key any more
describe() in Python was answering a different fast than the same date on the
command line: xerophagy on a Wednesday of the Great Fast read as wine_oil. Not a
cosmetic difference in a tool whose whole job is to say what may be eaten.

The cause is that Jurisdiction grew a tradition and a discipline, and config
carries all three — main.rs reads the saved jurisdiction, then the saved
tradition, then whether the Typikon's full discipline was asked for, and builds
an overridden Jurisdiction from the lot. The bindings resolved the key and
stopped, so everything the other two decide was silently dropped.

They now take the same three steps, and tradition= and typikon= are arguments, so
the library can say what the command line can. Pascha does not take them: it
follows the paschalion alone and neither can move it.

The overrides are interned rather than leaked per call. main.rs leaks one per run
and exits; a library gets called in a loop, and a leak per call adds up. The
combinations are bounded by jurisdictions x traditions x discipline, so the map
cannot grow past that.

The parity test spread widens to seven dates across both fasts and both
calendars, and clippy now runs under the feature as well — the binding code only
exists there, so a bare clippy run had never looked at it.
2026-09-18 12:33:56 -04:00
funkpower 43242c3837 carry sign and tradition through the Python bindings too
The bindings hand-map DayJson field by field, so the two fields added with
Greek and Slavic usage were in --json and not in to_dict(). The parity test
would have caught it; this is the fix rather than the discovery.

Both are getters as well, since every other field is.
2026-09-18 11:29:41 -04:00
funkpower 5d7e02cffb answer from Python too
The engine is the same; only the front door is new. pyo3 behind an optional
feature, maturin for the wheel, and the bindings mirror DayJson rather than Day
because DayJson is already the shape front ends read and the one that has to stay
still. to_dict() therefore gives exactly what --json gives, and a test holds the
two to that.

That test earned its place immediately. The bindings were calling
jurisdictions::resolve(None), which answers with a built-in default that knows
nothing about the machine, so Python was reckoning by the New Calendar while the
command line on the same box reckoned by ROCOR — every field downstream of the
calendar differed, including the fast. They now take the same two steps main.rs
takes, and agree field for field on Pascha, the Nativity and Theophany.

The wheel is abi3, so one build serves 3.9 upward rather than one per
interpreter. That costs the datetime C API, which the limited API does not
expose, so a date is read through its attributes instead — which incidentally
makes datetime.datetime work as well as datetime.date. Constructing dates is
unaffected, pyo3 calls Python's own constructor for that.

panic = "abort" comes out of the release profile. An extension module has to be
able to unwind a panic into a Python exception; aborting would take the
interpreter down with it. The CLI keeps the same behaviour it always had, a
panic there just unwinds now.

Mixed layout so the wheel can carry py.typed and a stub file, which a bare
extension module has nowhere to put. mypy --strict is clean against the stubs
and catches a deliberate error, so the Typing :: Typed classifier is not a claim
about nothing.
2026-09-18 11:28:08 -04:00