mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-23 03:07:12 +00:00
rebuild the engine in Rust
Python was never the house language here: the mw-* desktop surface it came
from is 39 bash scripts and was exactly one Python script, the one this was
extracted from. What it cost was distribution, a widget dependency that
needed pipx and a Python floor, and 24ms of interpreter startup before a line
of the program ran.
The port is verified rather than asserted. Both implementations were run over
199,446 day-records, 26 jurisdictions across 21 years, with every field of
every record required to match; the JSON, the iCalendar and 480 renderings of
the text output are identical too. examples/dump.rs is the harness.
What the type system actually buys, which is less than the usual claim and
more than nothing:
- Level is an ordered enum, strictest first, so reads as 'a is
stricter' and relaxed_to() is the one place a feast can lift a fast. No
rule can get the direction backwards.
- Date is a newtype over the Julian Day Number and JulianDate is a separate
type, so a civil date and an ecclesiastical one cannot be mixed up. That
confusion is the single most common way calendar code goes wrong.
- Octets is a newtype in the iCalendar folder, because the bug it replaced
counted characters against a limit RFC 5545 states in octets.
- Movables is a struct, not a map, so a mistyped key is a compile error
rather than a None at the far end of the program.
Of the six defects an adversarial review found in the Python, exactly one had
a shape a type system would have caught. The rest were logic, and the tests
came with them.
The menologion is compiled in with include_str!, so the binary answers on its
own: 930KB, no runtime, 1.2ms to start against Python's 36ms.
This commit is contained in:
@@ -23,18 +23,23 @@ $ typikon
|
||||
|
||||
## Install
|
||||
|
||||
One static binary, no runtime to install first.
|
||||
|
||||
```console
|
||||
$ pipx install git+https://git.pumpin.online/funkpower/typikon
|
||||
$ cargo install --git https://git.pumpin.online/funkpower/typikon
|
||||
```
|
||||
|
||||
Or run it straight out of a checkout. It has no dependencies and needs only
|
||||
Python 3.9.
|
||||
Or build it from a checkout:
|
||||
|
||||
```console
|
||||
$ git clone https://git.pumpin.online/funkpower/typikon
|
||||
$ cd typikon && python -m typikon.cli
|
||||
$ cd typikon && cargo build --release
|
||||
$ install -Dm755 target/release/typikon ~/.local/bin/typikon
|
||||
```
|
||||
|
||||
The menologion is compiled in, so the binary answers for every day of the year
|
||||
on its own: no data file to find, no network, nothing to go stale.
|
||||
|
||||
## Which calendar?
|
||||
|
||||
Three things get conflated constantly, and keeping them apart is most of what
|
||||
@@ -126,20 +131,30 @@ neighbour.
|
||||
|
||||
## As a library
|
||||
|
||||
```python
|
||||
import typikon
|
||||
```rust
|
||||
use typikon::calendars::Date;
|
||||
use typikon::day::describe;
|
||||
use typikon::jurisdictions::resolve;
|
||||
|
||||
d = typikon.today("rocor")
|
||||
d.church_date # '5 September'
|
||||
d.fast # 'xerophagy'
|
||||
d.title # 'The Holy Prophet Zachariah, father of the Forerunner'
|
||||
d.is_obligatory # False
|
||||
let rocor = resolve(Some("rocor"))?;
|
||||
let day = describe(Date::from_ymd(2026, 9, 18).unwrap(), rocor);
|
||||
|
||||
typikon.julian_pascha(2027) # datetime.date(2027, 5, 2)
|
||||
typikon.gregorian_pascha(2027) # datetime.date(2027, 3, 28)
|
||||
day.church_date; // "5 September"
|
||||
day.fast; // Level::Xerophagy
|
||||
day.title(); // "The Holy Prophet Zachariah, father of the Forerunner"
|
||||
day.is_obligatory(); // false
|
||||
|
||||
for day in typikon.week(date, 7, "antiochian"):
|
||||
...
|
||||
typikon::paschalion::julian_pascha(2027); // 2027-05-02
|
||||
typikon::paschalion::gregorian_pascha(2027); // 2027-03-28
|
||||
```
|
||||
|
||||
The levels are an ordered enum, strictest first, so `a < b` reads as "a is
|
||||
stricter than b" and a feast can only ever relax a fast:
|
||||
|
||||
```rust
|
||||
Level::Xerophagy.relaxed_to(Level::Fish, Level::WineOil) // WineOil: no saint
|
||||
// brings fish into Lent
|
||||
Level::Fish.relaxed_to(Level::WineOil, Level::WineOil) // Fish: never tightens
|
||||
```
|
||||
|
||||
## The menologion
|
||||
@@ -150,7 +165,7 @@ church both keep the Nativity on 25 December; they disagree about which morning
|
||||
that is, and that disagreement lives in the calendar conversion, not in the
|
||||
data.
|
||||
|
||||
`typikon/data/menologion.json` covers all 366 days with the principal
|
||||
`data/menologion.json`, compiled into the binary, covers all 366 days with the principal
|
||||
commemorations of the common Byzantine tradition, ranked so the fasting rules
|
||||
know when a feast lifts a Wednesday. It was composed for this project and is
|
||||
under the same licence as the code. It is a working set, not a complete
|
||||
@@ -165,14 +180,18 @@ $ typikon --list-menaia
|
||||
$ typikon --update-menaion ruthenian
|
||||
```
|
||||
|
||||
It lands beside the packaged menologion and is overlaid automatically.
|
||||
It lands in `~/.local/share/typikon/` and is overlaid automatically.
|
||||
|
||||
## Accuracy
|
||||
|
||||
`typikon` is pinned by 60 tests against published calendars: the paschalion
|
||||
for six years by both reckonings, the Julian drift across the 2100 boundary,
|
||||
the fast on every day that is not open to interpretation, and a full year in
|
||||
every jurisdiction.
|
||||
`typikon` is pinned against published calendars: the paschalion for six years
|
||||
by both reckonings and its invariants across two centuries, 146,000 calendar
|
||||
round-trips from 1800 to 2200, the fast on every day that is not open to
|
||||
interpretation, and every jurisdiction over twenty years.
|
||||
|
||||
It was ported from a Python implementation, and the port was checked by
|
||||
running both over 199,446 day-records, 26 jurisdictions across 21 years, and
|
||||
requiring every field of every record to match.
|
||||
|
||||
It is a calendar, not an authority. If it disagrees with the one your parish
|
||||
prints, your parish is right. Please open an issue.
|
||||
@@ -187,4 +206,4 @@ widgets for Hyprland (Quickshell), waybar and eww, all reading the JSON above.
|
||||
## Licence
|
||||
|
||||
GPL-2.0-only. See [LICENSE](LICENSE). The menologion is under it too;
|
||||
`typikon/data/LICENSE` says so and notes what the fetched sets are not.
|
||||
`data/LICENSE` says so and notes what the fetched sets are not.
|
||||
|
||||
Reference in New Issue
Block a user