Files

104 lines
4.2 KiB
Rust
Raw Permalink Normal View History

//! 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"
);
}