mirror of
https://git.pumpin.online/funkpower/typikon.git
synced 2026-10-23 11:07:16 +00:00
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.
88 lines
3.9 KiB
YAML
88 lines
3.9 KiB
YAML
name: ci
|
|
|
|
# Deliberately not triggered by pull_request.
|
|
#
|
|
# The forge this runs on is public and takes pull requests from strangers, and a
|
|
# runner executes whatever the workflow checks out. Triggering on pull_request
|
|
# would mean a stranger's first contribution runs code on the runner host before
|
|
# anyone has read it. Triggering only on push means the runner only ever executes
|
|
# code that someone with push rights put there.
|
|
#
|
|
# The cost is that a pull request gets no automatic check. Run it by pushing the
|
|
# branch yourself once you have read the diff.
|
|
on:
|
|
push:
|
|
# Branches only. A tag push does not match a branch filter, so release.yml
|
|
# owns tags and this owns everything else.
|
|
branches: ['**']
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: docker
|
|
container:
|
|
image: rust:1.98-bookworm
|
|
steps:
|
|
# Not actions/checkout. That is a JavaScript action: the runner executes it
|
|
# with `node` *inside this container*, and a Rust image carries no node, so
|
|
# the job dies at the first step with "node: executable file not found in
|
|
# $PATH". actions/cache and actions/upload-artifact are the same. Using an
|
|
# image that also carries node would mean installing Rust on every run;
|
|
# cloning by hand costs four lines and keeps the lean image.
|
|
- name: Checkout
|
|
run: |
|
|
git init -q .
|
|
git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
|
git fetch -q --depth 1 origin "$GITHUB_SHA"
|
|
git checkout -q FETCH_HEAD
|
|
|
|
# No `cargo fmt --check`. The tables in this crate are aligned by hand —
|
|
# MONTHS, JURISDICTIONS — and rustfmt wants one entry per line, which loses
|
|
# the shape that makes them readable. A gate that fights the house style is
|
|
# a gate that gets ignored.
|
|
- name: Lint
|
|
run: |
|
|
rustup component add clippy
|
|
cargo clippy --all-targets -- -D warnings
|
|
# The binding code only exists under the feature, so a bare clippy
|
|
# run never looks at src/python.rs.
|
|
cargo clippy --features python --all-targets -- -D warnings
|
|
|
|
- name: Rust tests
|
|
run: cargo test
|
|
|
|
# The CLI and the bindings are the same engine, so they have to agree. This
|
|
# is the check that caught the bindings resolving their own default
|
|
# jurisdiction while the CLI read the configured one.
|
|
- name: Python bindings
|
|
run: |
|
|
apt-get update && apt-get install -y --no-install-recommends python3 python3-venv python3-dev
|
|
python3 -m venv /tmp/venv
|
|
/tmp/venv/bin/pip install --quiet maturin pytest mypy
|
|
cargo build --release
|
|
/tmp/venv/bin/maturin build --release --out dist
|
|
/tmp/venv/bin/pip install --quiet --no-index --find-links dist typikon
|
|
/tmp/venv/bin/python -m pytest tests/python -q
|
|
# The bindings resolve the default jurisdiction themselves, and once
|
|
# read only the config file while the CLI read the environment first.
|
|
# The two then answered for different churches on the same machine.
|
|
cli=$(TYPIKON_JURISDICTION=rocor ./target/release/typikon \
|
|
--date 2026-03-18 --format tsv --fields jurisdiction | tail -1)
|
|
py=$(TYPIKON_JURISDICTION=rocor /tmp/venv/bin/python -c "import typikon, datetime; print(typikon.describe(datetime.date(2026, 3, 18)).jurisdiction)")
|
|
test "$cli" = "$py" || { echo "cli=$cli python=$py"; exit 1; }
|
|
|
|
- name: Type stubs
|
|
run: |
|
|
cat > /tmp/typecheck.py <<'EOF'
|
|
import datetime, typikon
|
|
d: typikon.Day = typikon.describe(datetime.date(2026, 4, 12))
|
|
day: datetime.date = d.date
|
|
feast: str | None = d.feast
|
|
n: int = d.days_to_pascha
|
|
EOF
|
|
/tmp/venv/bin/python -m mypy --strict /tmp/typecheck.py
|
|
|
|
- name: README examples
|
|
run: |
|
|
cargo build --release
|
|
python3 tests/readme-examples.py
|