Skip to main content

altium_format/ops/
intlib.rs

1// SPDX-License-Identifier: GPL-3.0-only
2// SPDX-FileCopyrightText: 2026 Alexander Kiselev <alex@akiselev.com>
3//
4//! Integrated library operations.
5//!
6//! High-level operations for exploring Altium integrated library (.IntLib) files.
7
8use std::collections::HashMap;
9use std::fs::File;
10use std::io::BufReader;
11use std::path::Path;
12
13use crate::io::IntLib;
14use crate::ops::output::*;
15
16fn open_intlib(path: &Path) -> Result<IntLib, Box<dyn std::error::Error>> {
17    let file = BufReader::new(File::open(path)?);
18    let intlib = IntLib::open(file)?;
19    Ok(intlib)
20}
21
22pub fn cmd_overview(path: &Path, full: bool) -> Result<IntLibOverview, Box<dyn std::error::Error>> {
23    let intlib = open_intlib(path)?;
24
25    // Footprint summary
26    let mut footprint_counts: HashMap<String, usize> = HashMap::new();
27    for xref in &intlib.cross_refs {
28        *footprint_counts.entry(xref.footprint.clone()).or_insert(0) += 1;
29    }
30
31    let mut footprint_usage: Vec<_> = footprint_counts.into_iter().collect();
32    footprint_usage.sort_by(|a, b| b.1.cmp(&a.1));
33
34    let component_list: Vec<ComponentCrossRef> = intlib
35        .cross_refs
36        .iter()
37        .map(|xref| ComponentCrossRef {
38            name: xref.name.clone(),
39            description: xref.description.clone(),
40            footprint: xref.footprint.clone(),
41        })
42        .collect();
43
44    // If full details requested, include symbols, footprints, and parameters
45    let (symbols, footprints, parameters) = if full {
46        let symbols: Vec<SymbolSummary> = intlib
47            .schlib
48            .iter()
49            .map(|comp| SymbolSummary {
50                name: comp.name().to_string(),
51                description: comp.description().to_string(),
52                pin_count: comp.pin_count(),
53            })
54            .collect();
55
56        let footprints: Vec<FootprintSummary> = intlib
57            .pcblib
58            .iter()
59            .map(|comp| FootprintSummary {
60                name: comp.pattern.clone(),
61                description: comp.description.clone(),
62                pad_count: comp.pad_count(),
63            })
64            .collect();
65
66        let parameters: Vec<ComponentParameters> = intlib
67            .parameters
68            .iter()
69            .map(|p| {
70                let params = p
71                    .params
72                    .iter()
73                    .map(|(k, v)| (k.to_string(), v.as_str().to_string()))
74                    .collect();
75                ComponentParameters {
76                    component_name: p.name.clone(),
77                    params,
78                }
79            })
80            .collect();
81
82        (Some(symbols), Some(footprints), Some(parameters))
83    } else {
84        (None, None, None)
85    };
86
87    Ok(IntLibOverview {
88        path: path.display().to_string(),
89        version: intlib.version,
90        component_count: intlib.cross_refs.len(),
91        schematic_symbol_count: intlib.schematic_component_count(),
92        pcb_footprint_count: intlib.footprint_count(),
93        parameter_set_count: intlib.parameters.len(),
94        footprint_usage,
95        component_list,
96        symbols,
97        footprints,
98        parameters,
99    })
100}
101
102pub fn cmd_list(path: &Path) -> Result<IntLibComponentList, Box<dyn std::error::Error>> {
103    let intlib = open_intlib(path)?;
104
105    let components: Vec<ComponentCrossRef> = intlib
106        .cross_refs
107        .iter()
108        .map(|xref| ComponentCrossRef {
109            name: xref.name.clone(),
110            description: xref.description.clone(),
111            footprint: xref.footprint.clone(),
112        })
113        .collect();
114
115    Ok(IntLibComponentList { components })
116}
117
118pub fn cmd_search(
119    path: &Path,
120    query: &str,
121    limit: Option<usize>,
122) -> Result<IntLibSearchResults, Box<dyn std::error::Error>> {
123    let intlib = open_intlib(path)?;
124    let query_lower = query.to_lowercase();
125
126    let matches: Vec<ComponentCrossRef> = intlib
127        .cross_refs
128        .iter()
129        .filter(|xref| {
130            xref.name.to_lowercase().contains(&query_lower)
131                || xref.description.to_lowercase().contains(&query_lower)
132                || xref.footprint.to_lowercase().contains(&query_lower)
133        })
134        .map(|xref| ComponentCrossRef {
135            name: xref.name.clone(),
136            description: xref.description.clone(),
137            footprint: xref.footprint.clone(),
138        })
139        .collect();
140
141    let total_matches = matches.len();
142    let results = if let Some(limit) = limit {
143        matches.into_iter().take(limit).collect()
144    } else {
145        matches
146    };
147
148    Ok(IntLibSearchResults {
149        query: query.to_string(),
150        total_matches,
151        results,
152    })
153}
154
155pub fn cmd_info(path: &Path) -> Result<IntLibInfo, Box<dyn std::error::Error>> {
156    let intlib = open_intlib(path)?;
157
158    Ok(IntLibInfo {
159        path: path.display().to_string(),
160        version: intlib.version,
161        cross_ref_count: intlib.cross_refs.len(),
162        schematic_symbol_count: intlib.schematic_component_count(),
163        pcb_footprint_count: intlib.footprint_count(),
164        parameter_set_count: intlib.parameters.len(),
165    })
166}
167
168pub fn cmd_component(
169    path: &Path,
170    name: &str,
171    show_params: bool,
172) -> Result<IntLibComponentDetail, Box<dyn std::error::Error>> {
173    let intlib = open_intlib(path)?;
174
175    // Find cross-reference
176    let xref = intlib
177        .cross_refs
178        .iter()
179        .find(|x| x.name == name)
180        .ok_or_else(|| format!("Component '{}' not found", name))?;
181
182    // Find and show schematic symbol info
183    let symbol_info = intlib
184        .schlib
185        .iter()
186        .find(|c| c.name() == name)
187        .map(|comp| SymbolInfo {
188            pin_count: comp.pin_count(),
189            primitive_count: comp.primitive_count(),
190        });
191
192    // Find and show footprint info
193    let footprint_info = intlib
194        .pcblib
195        .iter()
196        .find(|c| c.pattern == xref.footprint)
197        .map(|comp| FootprintInfo {
198            pad_count: comp.pad_count(),
199            primitive_count: comp.primitives.len(),
200        });
201
202    let parameters = if show_params {
203        intlib
204            .parameters
205            .iter()
206            .find(|p| p.name == name)
207            .map(|params| {
208                params
209                    .params
210                    .iter()
211                    .map(|(key, value)| (key.to_string(), value.as_str().to_string()))
212                    .collect()
213            })
214    } else {
215        None
216    };
217
218    Ok(IntLibComponentDetail {
219        name: xref.name.clone(),
220        description: xref.description.clone(),
221        footprint: xref.footprint.clone(),
222        schlib_path: xref.schlib_path.clone(),
223        pcblib_path: xref.pcblib_path.clone(),
224        symbol_info,
225        footprint_info,
226        parameters,
227    })
228}
229
230pub fn cmd_crossrefs(
231    path: &Path,
232    footprint_filter: Option<&str>,
233) -> Result<IntLibComponentList, Box<dyn std::error::Error>> {
234    let intlib = open_intlib(path)?;
235
236    let refs: Vec<ComponentCrossRef> = intlib
237        .cross_refs
238        .iter()
239        .filter(|x| {
240            footprint_filter
241                .is_none_or(|filter| x.footprint.to_lowercase().contains(&filter.to_lowercase()))
242        })
243        .map(|xref| ComponentCrossRef {
244            name: xref.name.clone(),
245            description: xref.description.clone(),
246            footprint: xref.footprint.clone(),
247        })
248        .collect();
249
250    Ok(IntLibComponentList { components: refs })
251}
252
253pub fn cmd_symbols(path: &Path) -> Result<IntLibSymbolList, Box<dyn std::error::Error>> {
254    let intlib = open_intlib(path)?;
255
256    let symbols: Vec<SymbolSummary> = intlib
257        .schlib
258        .iter()
259        .map(|comp| SymbolSummary {
260            name: comp.name().to_string(),
261            description: comp.description().to_string(),
262            pin_count: comp.pin_count(),
263        })
264        .collect();
265
266    Ok(IntLibSymbolList { symbols })
267}
268
269pub fn cmd_footprints(path: &Path) -> Result<IntLibFootprintList, Box<dyn std::error::Error>> {
270    let intlib = open_intlib(path)?;
271
272    let footprints: Vec<FootprintSummary> = intlib
273        .pcblib
274        .iter()
275        .map(|comp| FootprintSummary {
276            name: comp.pattern.clone(),
277            description: comp.description.clone(),
278            pad_count: comp.pad_count(),
279        })
280        .collect();
281
282    Ok(IntLibFootprintList { footprints })
283}
284
285pub fn cmd_parameters(
286    path: &Path,
287    component_filter: Option<&str>,
288    keys_filter: Option<&str>,
289) -> Result<IntLibParameterList, Box<dyn std::error::Error>> {
290    let intlib = open_intlib(path)?;
291
292    let keys: Option<Vec<String>> =
293        keys_filter.map(|k| k.split(',').map(|s| s.trim().to_lowercase()).collect());
294
295    let params: Vec<ComponentParameters> = intlib
296        .parameters
297        .iter()
298        .filter(|p| {
299            component_filter
300                .is_none_or(|filter| p.name.to_lowercase().contains(&filter.to_lowercase()))
301        })
302        .map(|p| {
303            let filtered_params = p
304                .params
305                .iter()
306                .filter(|(key, _)| {
307                    keys.as_ref()
308                        .is_none_or(|k_vec| k_vec.iter().any(|k| key.to_lowercase().contains(k)))
309                })
310                .map(|(k, v)| (k.to_string(), v.as_str().to_string()))
311                .collect();
312            ComponentParameters {
313                component_name: p.name.clone(),
314                params: filtered_params,
315            }
316        })
317        .collect();
318
319    Ok(IntLibParameterList { parameters: params })
320}
321
322/// Extract the embedded SchLib to a standalone file.
323/// Returns success message.
324pub fn cmd_extract_schlib(
325    path: &Path,
326    output: &Path,
327) -> Result<String, Box<dyn std::error::Error>> {
328    let intlib = open_intlib(path)?;
329
330    intlib.schlib.save_to_file(output)?;
331
332    Ok(format!(
333        "Extracted SchLib to: {}\n  Components: {}",
334        output.display(),
335        intlib.schematic_component_count()
336    ))
337}
338
339/// Extract the embedded PcbLib to a standalone file.
340/// Returns success message.
341pub fn cmd_extract_pcblib(
342    path: &Path,
343    output: &Path,
344) -> Result<String, Box<dyn std::error::Error>> {
345    let intlib = open_intlib(path)?;
346
347    intlib.pcblib.save_to_file(output)?;
348
349    Ok(format!(
350        "Extracted PcbLib to: {}\n  Footprints: {}",
351        output.display(),
352        intlib.footprint_count()
353    ))
354}