mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-19 02:10:25 -05:00
commit
694ce5b42b
3 files changed
+68
-8
No files matched your search
+64
-4
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user