← Commit history

embed: find ds2sf-emitted files by suffix, not dir-name prefix — fixes empty (0-pin) pin→pad table when MPN≠dirname (Ray issue #3); bump 2.6.3

barrett-land ·3f14bc0ea3 ·3mo ago ·parent ba5058f
4 files changed +39−9
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.6.1"+version = "2.6.3" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time." 
VERSION+1−1
@@ -1 +1 @@-2.6.1+2.6.3
page.json+1−1
@@ -85,4 +85,4 @@       "label": "Browse parts"     }   ]-}\ No newline at end of file+}
src/main.rs+36−6
@@ -1809,17 +1809,47 @@ fn read_first(dir: &std::path::Path, names: &[String]) -> Option<String> {     None } +/// Find the first file in `dir` whose name ends with `suffix` (case-insensitive+/// on the suffix), returning its path. Results are sorted for determinism.+///+/// ds2sf names its emitted files by the MPN / symbolName (e.g.+/// `74HC595BQ,115-symbol.extracted.json`), which frequently differs — in case or+/// exact form — from the chip *directory* name (e.g. `74hc595bq-dhvqfn16`). So+/// deriving the filename from the dir name misses the file entirely. Matching by+/// suffix finds it regardless of the prefix.+fn find_by_suffix(dir: &std::path::Path, suffix: &str) -> Option<PathBuf> {+    let suf = suffix.to_lowercase();+    let mut hits: Vec<PathBuf> = fs::read_dir(dir).ok()?+        .filter_map(|e| e.ok().map(|e| e.path()))+        .filter(|p| p.file_name().and_then(|n| n.to_str())+            .map(|n| n.to_lowercase().ends_with(&suf)).unwrap_or(false))+        .collect();+    hits.sort();+    hits.into_iter().next()+}++/// Read a dir file by exact name (any of `names`) first, then fall back to the+/// first file matching `suffix`. Robust to MPN-vs-dirname prefix drift.+fn read_first_or_suffix(dir: &std::path::Path, names: &[String], suffix: &str) -> Option<String> {+    read_first(dir, names)+        .or_else(|| find_by_suffix(dir, suffix).and_then(|p| fs::read_to_string(p).ok()))+}+ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String> {     let mpn = dir.file_name().and_then(|s| s.to_str()).ok_or("bad --dir")?.to_string();     // renders-    let sym_svg = read_first(dir, &[format!("{mpn}-symbol.svg")])-        .map(|s| s)+    let sym_svg = read_first_or_suffix(dir, &[format!("{mpn}-symbol.svg")], "-symbol.svg")         .unwrap_or_else(|| "<div class=\"ph\">no symbol render in this directory</div>".into());-    let fp_svg = read_first(dir, &[format!("{mpn}-footprint.authoritative.svg"), format!("{mpn}-footprint.svg")])+    let fp_svg = read_first(dir,+            &[format!("{mpn}-footprint.authoritative.svg"), format!("{mpn}-footprint.svg")])+        .or_else(|| find_by_suffix(dir, "-footprint.authoritative.svg").and_then(|p| fs::read_to_string(p).ok()))+        .or_else(|| find_by_suffix(dir, "-footprint.svg").and_then(|p| fs::read_to_string(p).ok()))         .unwrap_or_else(|| "<div class=\"ph\">no footprint render in this directory</div>".into());     let threed = {         let cands = ["-3d-iso-named.png", "-3d-iso.png", "-3d-iso-lg.png"]-            .iter().map(|s| dir.join(format!("{mpn}{s}"))).find(|p| p.exists());+            .iter().map(|s| dir.join(format!("{mpn}{s}"))).find(|p| p.exists())+            .or_else(|| ["-3d-iso-named.png", "-3d-iso.png", "-3d-iso-lg.png"]+                .iter().find_map(|s| find_by_suffix(dir, s)));         match cands.and_then(|p| fs::read(&p).ok()) {             Some(bytes) => format!("<img src=\"data:image/png;base64,{}\">", b64(&bytes)),             None => "<div class=\"ph\">no 3D render in this directory</div>".into(),@@ -1827,7 +1857,7 @@ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String>     };     // optional pin→pad connects from a .lbr     let mut pin_to_pad: std::collections::HashMap<String, String> = std::collections::HashMap::new();-    if let Some(lbr) = read_first(dir, &[format!("{mpn}.lbr")]) {+    if let Some(lbr) = read_first_or_suffix(dir, &[format!("{mpn}.lbr")], ".lbr") {         if let Ok(re) = regex_lite::Regex::new(r#"<connect gate="[^"]*" pin="([^"]+)" pad="([^"]+)"/>"#) {             for cap in re.captures_iter(&lbr) {                 pin_to_pad.insert(cap[1].to_string(), cap[2].to_string());@@ -1837,7 +1867,7 @@ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String>     // pins + descriptions from the extracted symbol JSON     let mut pins_json = String::from("[]");     let mut pad_count = fp_svg.matches("adom-pad").count().max(0);-    if let Some(ext) = read_first(dir, &[format!("{mpn}-symbol.extracted.json")]) {+    if let Some(ext) = read_first_or_suffix(dir, &[format!("{mpn}-symbol.extracted.json")], "-symbol.extracted.json") {         if let Ok(v) = serde_json::from_str::<serde_json::Value>(&ext) {             if let Some(arr) = v.get("pins").and_then(|p| p.as_array()) {                 let rows: Vec<serde_json::Value> = arr.iter().map(|p| {