app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.
← Commit history
2.41.1: every export verifies its writes; a missing output directory is created (wiki #26)
7 files changed
+158−22
Cargo.lock+79−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.41.0"+version = "2.41.1" dependencies = [ "altium-codec", "anyhow",@@ -20,6 +20,7 @@ dependencies = [ "regex-lite", "serde", "serde_json",+ "sha2", "tiny_http", ] @@ -116,6 +117,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]]+name = "block-buffer"+version = "0.10.4"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"+dependencies = [+ "generic-array",+]+ [[package]] name = "bumpalo" version = "3.20.3"@@ -206,6 +216,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]]+name = "cpufeatures"+version = "0.2.17"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"+dependencies = [+ "libc",+]+ [[package]] name = "crc32fast" version = "1.5.0"@@ -215,6 +234,26 @@ dependencies = [ "cfg-if", ] +[[package]]+name = "crypto-common"+version = "0.1.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"+dependencies = [+ "generic-array",+ "typenum",+]++[[package]]+name = "digest"+version = "0.10.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"+dependencies = [+ "block-buffer",+ "crypto-common",+]+ [[package]] name = "flate2" version = "1.1.9"@@ -255,6 +294,16 @@ dependencies = [ "slab", ] +[[package]]+name = "generic-array"+version = "0.14.7"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"+dependencies = [+ "typenum",+ "version_check",+]+ [[package]] name = "heck" version = "0.5.0"@@ -290,6 +339,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]]+name = "libc"+version = "0.2.189"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"+ [[package]] name = "log" version = "0.4.32"@@ -413,6 +468,17 @@ dependencies = [ "zmij", ] +[[package]]+name = "sha2"+version = "0.10.9"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"+dependencies = [+ "cfg-if",+ "cpufeatures",+ "digest",+]+ [[package]] name = "simd-adler32" version = "0.3.9"@@ -454,6 +520,12 @@ dependencies = [ "log", ] +[[package]]+name = "typenum"+version = "1.20.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"+ [[package]] name = "unicode-ident" version = "1.0.24"@@ -476,6 +548,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]]+name = "version_check"+version = "0.9.5"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"+ [[package]] name = "wasm-bindgen" version = "0.2.126"
Cargo.toml+2−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.41.0"+version = "2.41.1" 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." @@ -9,6 +9,7 @@ name = "adom-lbr" path = "src/main.rs" [dependencies]+sha2 = "0.10" clap = { version = "4", features = ["derive"] } clap_complete = "4" regex-lite = "0.1"
VERSION+1−1@@ -1 +1 @@-2.41.0+2.41.1
bin/adom-lbr⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.41.0",+ "version": "2.41.1", "type": "app", "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {},
page.json+8−8@@ -1,8 +1,8 @@ { "slug": "adom-lbr",- "version": "2.41.0",+ "version": "2.41.1", "type": "app",- "description": "adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.",+ "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {}, "tags": [ "eda",@@ -61,15 +61,15 @@ "screenshots/**", "dockbar.json" ],- "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools — KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion — through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",+ "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools \u2014 KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion \u2014 through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.", "sample_prompts": [ { "prompt": "Convert this KiCad symbol and footprint to an Altium SchLib and PcbLib",- "label": "KiCad → Altium"+ "label": "KiCad \u2192 Altium" }, { "prompt": "Import this Altium footprint into the adom-lbr JSON",- "label": "Altium → JSON"+ "label": "Altium \u2192 JSON" }, { "prompt": "Bundle this part into a single Altium IntLib",@@ -77,15 +77,15 @@ }, { "prompt": "Convert this Altium SchLib and PcbLib to a KiCad symbol and footprint",- "label": "Altium → KiCad"+ "label": "Altium \u2192 KiCad" }, { "prompt": "Export this adom-lbr JSON to a KiCad .kicad_sym and .kicad_mod",- "label": "JSON → KiCad"+ "label": "JSON \u2192 KiCad" }, { "prompt": "Open the adom-lbr Manager to browse wiki components and view the layer stackup", "label": "Browse parts" } ]-}+}
src/main.rs+67−10@@ -2899,6 +2899,41 @@ const EAGLE_LAYERS: &str = r#"<layers> // ── Main ── /// Read a text file or exit with a clear error.+/// Write `bytes` to `path` (creating the directory), read the file back and+/// require it to match, returning the sha256. Any failure is a coded error+/// and exit 2: export-kicad once printed "OK: wrote" for two files in a+/// directory that did not exist, because every write went through+/// `fs::write(..).ok()` and nothing created the directory (wiki #26).+fn write_verified(path: &std::path::Path, bytes: &[u8]) -> String {+ use sha2::{Digest, Sha256};+ if let Some(dir) = path.parent().filter(|d| !d.as_os_str().is_empty()) {+ if let Err(e) = fs::create_dir_all(dir) {+ eprintln!("ERROR: [OUTPUT_NOT_WRITTEN] cannot create {}: {e}", dir.display());+ process::exit(2);+ }+ }+ if let Err(e) = fs::write(path, bytes) {+ eprintln!("ERROR: [OUTPUT_NOT_WRITTEN] cannot write {}: {e}", path.display());+ process::exit(2);+ }+ match fs::read(path) {+ Ok(back) if back == bytes => format!("{:x}", Sha256::digest(bytes)),+ Ok(back) => {+ eprintln!("ERROR: [OUTPUT_NOT_WRITTEN] {} read back {} bytes, expected {}", path.display(), back.len(), bytes.len());+ process::exit(2);+ }+ Err(e) => {+ eprintln!("ERROR: [OUTPUT_NOT_WRITTEN] {} was written but cannot be read back: {e}", path.display());+ process::exit(2);+ }+ }+}++/// The per-file receipt after a verified write.+fn written_hint(path: &std::path::Path, bytes: usize, sha: &str) {+ println!("Hint: verified {} ({bytes} bytes, sha256 {sha}, read back identical)", path.display());+}+ fn read_or_exit(path: &std::path::Path) -> String { fs::read_to_string(path).unwrap_or_else(|e| { eprintln!("ERROR: read {}: {e}", path.display());@@ -3488,8 +3523,10 @@ The geometry is fine but a BOM cannot order this part - pass --part-metadata or } } let outp = out.unwrap_or_else(|| PathBuf::from(format!("{mpn}.adom-lbr.json")));- fs::write(&outp, serde_json::to_string_pretty(&part).unwrap()).ok();+ let json_out = serde_json::to_string_pretty(&part).unwrap();+ let sha = write_verified(&outp, json_out.as_bytes()); println!("OK: wrote {} (adom-lbr JSON — the canonical hub)", outp.display());+ written_hint(&outp, json_out.len(), &sha); println!("Hint: convert it with `adom-lbr export-altium {}` or `export-intlib`.", outp.display()); } @@ -3504,10 +3541,12 @@ The geometry is fine but a BOM cannot order this part - pass --part-metadata or .unwrap_or_else(|e| { eprintln!("ERROR: {e:#}"); process::exit(1); }); let sp = out_dir.join(format!("{}.SchLib", p.mpn)); let pp = out_dir.join(format!("{}.PcbLib", p.mpn));- fs::write(&sp, &sym_bytes).ok();- fs::write(&pp, &fp_bytes).ok();+ let sha_s = write_verified(&sp, &sym_bytes);+ let sha_p = write_verified(&pp, &fp_bytes); println!("OK: wrote {} ({} pins) + {} ({} pads)", sp.display(), p.symbol.pins.len(), pp.display(), p.footprint.pads.len());+ written_hint(&sp, sym_bytes.len(), &sha_s);+ written_hint(&pp, fp_bytes.len(), &sha_p); } Commands::AssembleAltium { out, parts } => {@@ -3670,8 +3709,9 @@ The geometry is fine but a BOM cannot order this part - pass --part-metadata or let bytes = altium_codec::encode_intlib(&symbol, &p.footprint, &descr) .unwrap_or_else(|e| { eprintln!("ERROR: {e:#}"); process::exit(1); }); let outp = out.unwrap_or_else(|| PathBuf::from(format!("{}.IntLib", p.mpn)));- fs::write(&outp, &bytes).ok();+ let sha = write_verified(&outp, &bytes); println!("OK: wrote {} ({} bytes) — symbol + footprint bundled", outp.display(), bytes.len());+ written_hint(&outp, bytes.len(), &sha); } Commands::ExportKicad { part, out_dir } => {@@ -3682,11 +3722,13 @@ The geometry is fine but a BOM cannot order this part - pass --part-metadata or let fp_txt = altium_codec::export_kicad_footprint(&p.footprint); let sp = out_dir.join(format!("{}.kicad_sym", p.mpn)); let fp = out_dir.join(format!("{}.kicad_mod", p.mpn));- fs::write(&sp, sym_txt).ok();- fs::write(&fp, fp_txt).ok();+ let sha_s = write_verified(&sp, sym_txt.as_bytes());+ let sha_f = write_verified(&fp, fp_txt.as_bytes()); println!("OK: wrote {} ({} pins) + {} ({} pads, {} graphics)", sp.display(), p.symbol.pins.len(), fp.display(), p.footprint.pads.len(), p.footprint.graphics.len());+ written_hint(&sp, sym_txt.len(), &sha_s);+ written_hint(&fp, fp_txt.len(), &sha_f); } Commands::AddToSchlib { existing, part, out } => {@@ -3705,8 +3747,9 @@ The geometry is fine but a BOM cannot order this part - pass --part-metadata or let merged = altium_codec::add_symbols_to_schlib(&existing_bytes, &syms) .unwrap_or_else(|e| { eprintln!("ERROR: {e:#}"); process::exit(1); }); let outp = out.unwrap_or_else(|| PathBuf::from("merged.SchLib"));- fs::write(&outp, &merged).ok();+ let sha = write_verified(&outp, &merged); println!("OK: wrote {} — added {} symbol(s) into the library", outp.display(), syms.len());+ written_hint(&outp, merged.len(), &sha); } Commands::ImportAltium { file, pcblib, schlib, mpn, out } => {@@ -3731,16 +3774,17 @@ The geometry is fine but a BOM cannot order this part - pass --part-metadata or .unwrap_or_else(|e| { eprintln!("ERROR: {e:#}"); process::exit(1); }); let name = if part.mpn.is_empty() { "part".to_string() } else { part.mpn.clone() }; let outp = out.unwrap_or_else(|| PathBuf::from(format!("{name}.adom-lbr.json")));- fs::write(&outp, serde_json::to_string_pretty(&part).unwrap()).ok();+ let json_out = serde_json::to_string_pretty(&part).unwrap();+ let sha = write_verified(&outp, json_out.as_bytes()); println!("OK: wrote {} — {} pins, {} pads, {} footprint graphics", outp.display(), part.symbol.pins.len(), part.footprint.pads.len(), part.footprint.graphics.len());+ written_hint(&outp, json_out.len(), &sha); } Commands::Install => { let home = std::env::var("HOME").unwrap_or_else(|_| "/home/adom".to_string()); let skill_dir = PathBuf::from(&home).join(".claude/skills/adom-lbr");- fs::create_dir_all(&skill_dir).ok();- fs::write(skill_dir.join("SKILL.md"), SKILL).ok();+ write_verified(&skill_dir.join("SKILL.md"), SKILL.as_bytes()); println!("Installed skill: adom-lbr -> {}", skill_dir.display()); println!("Installed adom-lbr v{}", VERSION.trim()); }@@ -4067,6 +4111,19 @@ fn cmd_embed(dir: &std::path::Path, out: Option<PathBuf>) -> Result<(), String> #[cfg(test)] mod lint_tests {+ #[test]+ fn write_verified_creates_the_directory_and_returns_the_hash() {+ use sha2::{Digest, Sha256};+ let dir = std::env::temp_dir().join(format!("adom-lbr-wv-{}-{}", std::process::id(),+ std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()));+ let path = dir.join("nested").join("out.txt");+ assert!(!dir.exists(), "fresh temp dir must not pre-exist");+ let sha = super::write_verified(&path, b"hello export");+ assert_eq!(std::fs::read(&path).unwrap(), b"hello export", "the file must exist with the bytes");+ assert_eq!(sha, format!("{:x}", Sha256::digest(b"hello export")));+ let _ = std::fs::remove_dir_all(&dir);+ }+ use super::{dedup_pad_names, lint_lbr}; // ---- 2.21.0 / 2.22.x conversion fidelity -------------------------------------