Files
funkpower 93b597302a 1.1.2 — one sentence, in all five places a reader meets it
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.
2026-09-21 07:46:58 -04:00

104 lines
4.2 KiB
Rust

//! 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::<Vec<_>>().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"
);
}