From 93b597302a874fbd0cc7a8294f0b7fe00b3abd9b Mon Sep 17 00:00:00 2001 From: funkpower Date: Mon, 21 Sep 2026 07:46:58 -0400 Subject: [PATCH] =?UTF-8?q?1.1.2=20=E2=80=94=20one=20sentence,=20in=20all?= =?UTF-8?q?=20five=20places=20a=20reader=20meets=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.1.1 changed the description in Cargo.toml and in the npm package and missed pyproject.toml, which is the one maturin publishes. PyPI took the old summary and neither registry lets that be edited afterwards, so the correction needs a version of its own. The two READMEs had drifted further than the description fields. A registry page leads with the README rather than the summary, and npm's opened by saying the package needs no network without ever saying it covers the Byzantine Catholic churches — which is most of what the sentence was changed to convey. Five files now carry it, and a test reads all five and names the ones that disagree. It was written against the mistake above and fails on it. --- CHANGELOG.md | 11 +++++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 4 +- npm/README.md | 5 ++- pyproject.toml | 2 +- tests/metadata.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 tests/metadata.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 31a6858..b3702f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Notable changes, in the terms [docs/versioning.md](docs/versioning.md) sets out. **Calendar** entries change what a day reports, whatever the version number does. +## 1.1.2 + +### Fixed + +- The published description says what the package covers, and says the same + thing in both registries. PyPI takes its summary from `pyproject.toml`, which + 1.1.1 did not update, so it went out with the old one-line text while npm got + the new one. The two READMEs had drifted apart as well: npm's opened without + mentioning the Byzantine Catholic churches at all. All four now carry one + sentence, and a test fails if any of them moves without the others. + ## 1.1.1 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 1b09d27..a474676 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,7 +404,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "typikon" -version = "1.1.1" +version = "1.1.2" dependencies = [ "clap", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index ee78538..d4e543d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "typikon" -version = "1.1.1" +version = "1.1.2" edition = "2021" rust-version = "1.85" description = "The Orthodox liturgical day: feasts, seasons and the fast, computed rather than fetched. Twenty Orthodox jurisdictions and four Byzantine Catholic churches, across the Julian, Revised Julian and Gregorian reckonings." diff --git a/README.md b/README.md index 7417c92..f14e229 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # typikon The Orthodox liturgical day: feasts, seasons and the fast, computed rather -than fetched. The Byzantine Catholic churches are here too, on their own -calendars and, where the law has been sourced, under their own particular law. +than fetched. Twenty Orthodox jurisdictions and four Byzantine Catholic +churches, across the Julian, Revised Julian and Gregorian reckonings. Everything derives from the paschalion and the fixed calendar, so it needs no network, cannot go stale, and answers for any year you ask about. No API key, diff --git a/npm/README.md b/npm/README.md index 7173659..b0e8770 100644 --- a/npm/README.md +++ b/npm/README.md @@ -1,7 +1,10 @@ # typikon The Orthodox liturgical day: feasts, seasons and the fast, computed rather -than fetched. No network, no data files, no service to be up. +than fetched. Twenty Orthodox jurisdictions and four Byzantine Catholic +churches, across the Julian, Revised Julian and Gregorian reckonings. + +No network, no data files, no service to be up. ```sh npm install typikon diff --git a/pyproject.toml b/pyproject.toml index 56067ed..0fb13f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "typikon" -description = "The Orthodox liturgical day: feasts, seasons and the fast, computed rather than fetched" +description = "The Orthodox liturgical day: feasts, seasons and the fast, computed rather than fetched. Twenty Orthodox jurisdictions and four Byzantine Catholic churches, across the Julian, Revised Julian and Gregorian reckonings." readme = "README.md" license = "GPL-2.0-only" license-files = ["LICENSE"] diff --git a/tests/metadata.rs b/tests/metadata.rs new file mode 100644 index 0000000..8c24c49 --- /dev/null +++ b/tests/metadata.rs @@ -0,0 +1,103 @@ +//! The packaging metadata, which lives in three files and is published to two +//! registries. +//! +//! `Cargo.toml` describes the crate, `pyproject.toml` is what maturin puts on +//! PyPI, and `npm/package.json.in` is what npm shows. Nothing makes them agree +//! and nothing notices when they stop: a description edited in one of the three +//! reaches one registry and not the other, and the wrong one is then frozen +//! into a release, because neither registry lets published metadata be edited. +//! That has happened once already. +//! +//! The two READMEs open with the same sentence, because that paragraph is the +//! first thing shown on both registry pages and is what most people will read +//! instead of the description field. + +use std::fs; + +/// The value of a `key = "…"` line, from the first section that has one. +fn toml_string(path: &str, key: &str) -> String { + let text = fs::read_to_string(path).unwrap_or_else(|e| panic!("{path}: {e}")); + for line in text.lines() { + let line = line.trim(); + let Some(rest) = line.strip_prefix(key) else { continue }; + let Some(rest) = rest.trim_start().strip_prefix('=') else { continue }; + let rest = rest.trim(); + if let Some(v) = rest.strip_prefix('"').and_then(|v| v.strip_suffix('"')) { + return v.to_string(); + } + } + panic!("{path}: no `{key} = \"…\"` line"); +} + +/// The value of a `"key": "…"` line. Enough for a hand-written file with no +/// escapes in it, and the test below fails loudly rather than quietly if that +/// ever stops being true. +fn json_string(path: &str, key: &str) -> String { + let text = fs::read_to_string(path).unwrap_or_else(|e| panic!("{path}: {e}")); + let needle = format!("\"{key}\":"); + for line in text.lines() { + let Some(rest) = line.trim().strip_prefix(&needle) else { continue }; + let rest = rest.trim().trim_end_matches(',').trim(); + if let Some(v) = rest.strip_prefix('"').and_then(|v| v.strip_suffix('"')) { + assert!(!v.contains('\\'), "{path}: {key} has escapes this reader cannot handle"); + return v.to_string(); + } + } + panic!("{path}: no `\"{key}\": \"…\"` line"); +} + +#[test] +fn the_three_descriptions_are_the_same_sentence() { + let cargo = toml_string("Cargo.toml", "description"); + let pyproject = toml_string("pyproject.toml", "description"); + let npm = json_string("npm/package.json.in", "description"); + + assert_eq!( + cargo, pyproject, + "Cargo.toml and pyproject.toml disagree; pyproject.toml is the one PyPI shows" + ); + assert_eq!( + cargo, npm, + "Cargo.toml and npm/package.json.in disagree; the latter is the one npm shows" + ); +} + +#[test] +fn the_description_says_what_the_crate_is_called() { + // Cheap guard against a description edited into something that no longer + // describes this package at all. + let d = toml_string("Cargo.toml", "description"); + assert!(d.len() > 40, "description is too short to be one: {d:?}"); + assert!(!d.ends_with(' '), "description has a trailing space"); + assert!(d.contains("Orthodox"), "description no longer mentions Orthodox: {d:?}"); +} + +/// The opening paragraph of a README, unwrapped to one line. +fn lede(path: &str) -> String { + let text = fs::read_to_string(path).unwrap_or_else(|e| panic!("{path}: {e}")); + let body = text + .split("\n\n") + .find(|p| !p.trim().is_empty() && !p.trim_start().starts_with('#')) + .unwrap_or_else(|| panic!("{path}: no opening paragraph")); + body.split_whitespace().collect::>().join(" ") +} + +#[test] +fn both_readmes_open_with_the_description() { + // A registry page leads with the README, not with the description field, + // so the sentence a reader actually meets is this one. npm's README is a + // separate file from the crate's and had gone its own way: it described + // the package without mentioning that it covers Byzantine Catholic + // churches at all. + let description = toml_string("Cargo.toml", "description"); + assert_eq!( + lede("README.md"), + description, + "README.md does not open with the published description" + ); + assert_eq!( + lede("npm/README.md"), + description, + "npm/README.md does not open with the published description" + ); +}