mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
Merge upstream/main into pr/longtail-raws
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -135,6 +135,8 @@ mod tests {
|
||||
gpu vs cpu, loupe 1920×1280 typical: max 1 LSB, mean 0.0001 LSB
|
||||
loupe draft 1152×768 cold: cpu: 379.5 ms wall, 139.5 ms cpu | gpu: 19.9 ms wall, 17.2 ms cpu
|
||||
export JPEG encode: 63.9 ms wall, 278.0 ms cpu
|
||||
window 3072×2304 at (3328, 2048) of 9984×6656
|
||||
window pan (new window, warm src): cpu: 912.0 ms wall, 2210.5 ms cpu | gpu: 37.5 ms wall, 8.7 ms cpu
|
||||
";
|
||||
|
||||
#[test]
|
||||
@@ -143,7 +145,10 @@ export JPEG encode: 63.9 ms wall, 278.0 ms cpu
|
||||
assert_eq!(m["loupe draft 1152×768 cold/cpu/wall"], 379.5);
|
||||
assert_eq!(m["loupe draft 1152×768 cold/gpu/cpu"], 17.2);
|
||||
assert_eq!(m["export JPEG encode/cpu/cpu"], 278.0);
|
||||
assert_eq!(m.len(), 6, "{m:?}");
|
||||
// (a comma in the name is no column separator)
|
||||
assert_eq!(m["window pan (new window, warm src)/cpu/cpu"], 2210.5);
|
||||
assert_eq!(m["window pan (new window, warm src)/gpu/wall"], 37.5);
|
||||
assert_eq!(m.len(), 10, "{m:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -524,6 +524,26 @@ const RAW_SAMPLES: &[(&str, &str, &str)] = &[
|
||||
"https://raw.pixls.us/getfile.php/6587/nice/Canon%20-%20EOS%20R8%20-%203:2.CR3",
|
||||
"df33cf394573645ce03dea1b2e9f0b5cc2b7734e9e3391ee7114151414cf2812",
|
||||
),
|
||||
(
|
||||
"cr3-canon-rp-raw.cr3",
|
||||
"https://raw.pixls.us/getfile.php/4674/nice/Canon%20-%20EOS%20RP%20-%203:2.CR3",
|
||||
"dcbd0f6f9929de2e2331b0fd10a9d01e7cfd04c5a5e4038456de1647d714cdf2",
|
||||
),
|
||||
(
|
||||
"cr3-canon-rp-craw.cr3",
|
||||
"https://raw.pixls.us/getfile.php/4672/nice/Canon%20-%20EOS%20RP%20-%203:2.CR3",
|
||||
"bd3270610593d37ff8a399908bb9406a4e32f9151d55bf19ac1cf8f10d787b46",
|
||||
),
|
||||
(
|
||||
"cr3-canon-rp-raw-crop.cr3",
|
||||
"https://raw.pixls.us/getfile.php/4673/nice/Canon%20-%20EOS%20RP%20-%203:2.CR3",
|
||||
"7d4ba45dc9264d598336133593c677b09774bcdf13b602ee770e72415a652a16",
|
||||
),
|
||||
(
|
||||
"cr3-canon-rp-craw-crop.cr3",
|
||||
"https://raw.pixls.us/getfile.php/4671/nice/Canon%20-%20EOS%20RP%20-%203:2.CR3",
|
||||
"4a859ab137f792aedaf3416b0aad1497ebaab02052a49453902e226699154aa7",
|
||||
),
|
||||
// EOS R5 Mark II, full frame and 1.6x crop mode: odd-height (2825- / 1787-row) Bayer planes.
|
||||
(
|
||||
"cr3-canon-r5m2-raw.cr3",
|
||||
|
||||
+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