# Conflicts:
#	docs/gaps.md
This commit is contained in:
Brandon Thomas committed 2026-10-14 01:39:50 -04:00
commit 694ce5b42b
3 files changed
+68 -8

No files matched your search

+64 -4
View File
@@ -6,11 +6,14 @@
//! inside the file). A trailing `*` matches an id prefix. Row ids must be unique and statuses valid.
//! - **Summary**: per section done / partial / missing / out of scope and the share of P0 and P1 rows done;
//! `--write` refreshes the table between the `parity:summary` markers in the document.
//! - **Gaps** (in `ci`): the ranked table of `docs/gaps.md` lists each gap number once. Two
//! branches that both edit an entry leave both copies after a merge, and nothing else notices.
use std::collections::{BTreeSet, HashSet};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::path::Path;
pub const DOC: &str = "docs/parity-checklist.md";
pub const GAPS: &str = "docs/gaps.md";
const BEGIN: &str = "<!-- parity:summary -->";
const END: &str = "<!-- /parity:summary -->";
@@ -268,6 +271,33 @@ pub fn with_summary(md: &str, table: &str) -> Option<String> {
Some(format!("{}\n{}{}", &md[..b], table, &md[e..]))
}
/// Gap numbers listed more than once in the `## Ranked` table of `docs/gaps.md` (a row's first
/// cell is its number, `B` marking a beta blocker: `| 10 B | …`).
pub fn check_gaps(md: &str) -> Vec<String> {
let mut first: HashMap<u32, usize> = HashMap::new();
let mut problems = Vec::new();
let mut ranked = false;
for (i, line) in md.lines().enumerate() {
if let Some(heading) = line.strip_prefix("## ") {
ranked = heading.trim() == "Ranked";
continue;
}
let Some(cell) = line.strip_prefix('|').and_then(|r| r.split('|').next()) else { continue };
let cell = cell.trim();
let Ok(n) = cell.strip_suffix(" B").unwrap_or(cell).trim().parse::<u32>() else { continue };
if !ranked {
continue;
}
match first.get(&n) {
Some(at) => problems.push(format!("{GAPS}:{}: gap {n} is listed again (first at line {at}): keep one row per gap", i + 1)),
None => {
first.insert(n, i + 1);
}
}
}
problems
}
fn registry(what: &str) -> Result<BTreeSet<String>, String> {
let out = crate::cargo()
.args(["run", "-q", "-p", "lightcraft-cli", "--", what, "--json"])
@@ -288,10 +318,12 @@ pub fn run(root: &Path, write: bool) -> Result<(), String> {
let mut commands = registry("commands")?;
commands.extend(ui_command_ids(&menus));
let controls = registry("controls")?;
let problems = check(&doc, &commands, &controls, |p| {
let mut problems = check(&doc, &commands, &controls, |p| {
let full = root.join(p);
if full.is_dir() { Some(0) } else { std::fs::read_to_string(full).ok().map(|s| s.lines().count()) }
});
let gaps = std::fs::read_to_string(root.join(GAPS)).map_err(|e| format!("{GAPS}: {e}"))?;
problems.extend(check_gaps(&gaps));
let table = summary_table(&doc);
println!("Lightroom parity ({DOC}): {} rows, {} references\n", doc.rows.len(), doc.refs.len());
@@ -319,14 +351,14 @@ pub fn run(root: &Path, write: bool) -> Result<(), String> {
}
}
if problems.is_empty() {
println!("\nOK: every command, control and path referenced in {DOC} exists.");
println!("\nOK: every command, control and path referenced in {DOC} exists; {GAPS} lists each gap once.");
Ok(())
} else {
println!();
for p in &problems {
println!(" - {p}");
}
Err(format!("{} stale reference(s) in {DOC}", problems.len()))
Err(format!("{} problem(s) in {DOC} / {GAPS}", problems.len()))
}
}
@@ -388,6 +420,34 @@ old
assert!(d.errors.iter().any(|e| e.contains("KEY-SHORT")));
}
#[test]
fn gaps_are_listed_once() {
let md = "\
# Gaps
## Ranked
| # | Gap | Hours |
|---:|---|---:|
| 1 B | one | 3 |
| 2 | two | 4 |
| 2 | two, as the other branch left it | 5 |
| 10 B | ten | 6 |
| 10 B | ten again | 6 |
## Closed recently
| # | Gap |
|---|---|
| 1 | an old one, numbered as it was |
";
let p = check_gaps(md);
assert_eq!(p.len(), 2, "{p:?}");
assert!(p[0].contains(":9: gap 2 is listed again (first at line 8)"), "{p:?}");
assert!(p[1].contains(":11: gap 10 is listed again (first at line 10)"), "{p:?}");
assert!(check_gaps("## Ranked\n\n| 1 | a |\n| 2 B | b |\n").is_empty());
}
#[test]
fn collects_references_everywhere() {
let d = parse(SAMPLE);