Skip to main content

altium_format/api/generic/
container.rs

1//! Container types for collections of records.
2
3use crate::api::cfb::Block;
4use crate::records::pcb::PcbObjectId;
5use crate::types::ParameterCollection;
6
7use super::{BinaryRecord, GenericRecord};
8
9/// Container for parameter-format records.
10///
11/// Used for schematic files where records are pipe-delimited parameters.
12#[derive(Debug, Clone)]
13pub struct ParamsContainer {
14    /// Source stream path
15    source: String,
16    /// Records in order
17    records: Vec<GenericRecord>,
18}
19
20impl ParamsContainer {
21    /// Creates a container from a stream path and blocks.
22    pub fn from_blocks(source: &str, blocks: &[Block]) -> Self {
23        let mut records = Vec::with_capacity(blocks.len());
24
25        for block in blocks {
26            if let Some(params) = block.as_params() {
27                records.push(GenericRecord::from_params(&params));
28            }
29        }
30
31        ParamsContainer {
32            source: source.to_string(),
33            records,
34        }
35    }
36
37    /// Creates a container from raw parameter strings.
38    pub fn from_params_list(source: &str, params_list: Vec<ParameterCollection>) -> Self {
39        let records = params_list.iter().map(GenericRecord::from_params).collect();
40
41        ParamsContainer {
42            source: source.to_string(),
43            records,
44        }
45    }
46
47    /// Returns the source stream path.
48    pub fn source(&self) -> &str {
49        &self.source
50    }
51
52    /// Returns the number of records.
53    pub fn len(&self) -> usize {
54        self.records.len()
55    }
56
57    /// Returns true if there are no records.
58    pub fn is_empty(&self) -> bool {
59        self.records.is_empty()
60    }
61
62    /// Gets a record by index.
63    pub fn get(&self, index: usize) -> Option<&GenericRecord> {
64        self.records.get(index)
65    }
66
67    /// Gets a mutable record by index.
68    pub fn get_mut(&mut self, index: usize) -> Option<&mut GenericRecord> {
69        self.records.get_mut(index)
70    }
71
72    /// Iterates over all records.
73    pub fn iter(&self) -> impl Iterator<Item = &GenericRecord> {
74        self.records.iter()
75    }
76
77    /// Iterates mutably over all records.
78    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut GenericRecord> {
79        self.records.iter_mut()
80    }
81
82    /// Filters records by record ID.
83    pub fn filter_by_type(&self, record_id: i32) -> impl Iterator<Item = &GenericRecord> {
84        self.records
85            .iter()
86            .filter(move |r| r.record_id() == Some(record_id))
87    }
88
89    /// Finds the first record matching a predicate.
90    pub fn find<F>(&self, pred: F) -> Option<&GenericRecord>
91    where
92        F: Fn(&GenericRecord) -> bool,
93    {
94        self.records.iter().find(|r| pred(r))
95    }
96
97    /// Finds all records matching a predicate.
98    pub fn find_all<F>(&self, pred: F) -> Vec<&GenericRecord>
99    where
100        F: Fn(&GenericRecord) -> bool,
101    {
102        self.records.iter().filter(|r| pred(r)).collect()
103    }
104
105    /// Returns true if any records were modified.
106    pub fn is_modified(&self) -> bool {
107        self.records.iter().any(|r| r.is_modified())
108    }
109
110    /// Adds a record to the container.
111    pub fn push(&mut self, record: GenericRecord) {
112        self.records.push(record);
113    }
114
115    /// Removes a record by index.
116    pub fn remove(&mut self, index: usize) -> Option<GenericRecord> {
117        if index < self.records.len() {
118            Some(self.records.remove(index))
119        } else {
120            None
121        }
122    }
123
124    /// Converts all records back to parameter collections.
125    pub fn to_params_list(&self) -> Vec<ParameterCollection> {
126        self.records.iter().map(|r| r.to_params()).collect()
127    }
128}
129
130impl IntoIterator for ParamsContainer {
131    type Item = GenericRecord;
132    type IntoIter = std::vec::IntoIter<GenericRecord>;
133
134    fn into_iter(self) -> Self::IntoIter {
135        self.records.into_iter()
136    }
137}
138
139impl<'a> IntoIterator for &'a ParamsContainer {
140    type Item = &'a GenericRecord;
141    type IntoIter = std::slice::Iter<'a, GenericRecord>;
142
143    fn into_iter(self) -> Self::IntoIter {
144        self.records.iter()
145    }
146}
147
148/// Container for binary-format records.
149///
150/// Used for PCB files where records are size-prefixed binary blocks.
151#[derive(Debug, Clone)]
152pub struct BinaryContainer {
153    /// Source stream path
154    source: String,
155    /// Records in order
156    records: Vec<BinaryRecord>,
157}
158
159impl BinaryContainer {
160    /// Creates a container from raw binary blocks.
161    ///
162    /// Each block is expected to have object_id as the first byte.
163    pub fn from_blocks(source: &str, blocks: &[Block]) -> Self {
164        let mut records = Vec::with_capacity(blocks.len());
165
166        for block in blocks {
167            if block.data.is_empty() {
168                continue;
169            }
170
171            let object_id = PcbObjectId::from_byte(block.data[0]);
172            records.push(BinaryRecord::from_binary(object_id, block.data.clone()));
173        }
174
175        BinaryContainer {
176            source: source.to_string(),
177            records,
178        }
179    }
180
181    /// Creates an empty container.
182    pub fn new(source: &str) -> Self {
183        BinaryContainer {
184            source: source.to_string(),
185            records: Vec::new(),
186        }
187    }
188
189    /// Returns the source stream path.
190    pub fn source(&self) -> &str {
191        &self.source
192    }
193
194    /// Returns the number of records.
195    pub fn len(&self) -> usize {
196        self.records.len()
197    }
198
199    /// Returns true if there are no records.
200    pub fn is_empty(&self) -> bool {
201        self.records.is_empty()
202    }
203
204    /// Gets a record by index.
205    pub fn get(&self, index: usize) -> Option<&BinaryRecord> {
206        self.records.get(index)
207    }
208
209    /// Gets a mutable record by index.
210    pub fn get_mut(&mut self, index: usize) -> Option<&mut BinaryRecord> {
211        self.records.get_mut(index)
212    }
213
214    /// Iterates over all records.
215    pub fn iter(&self) -> impl Iterator<Item = &BinaryRecord> {
216        self.records.iter()
217    }
218
219    /// Iterates mutably over all records.
220    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut BinaryRecord> {
221        self.records.iter_mut()
222    }
223
224    /// Filters records by object ID.
225    pub fn filter_by_type(&self, object_id: PcbObjectId) -> impl Iterator<Item = &BinaryRecord> {
226        self.records
227            .iter()
228            .filter(move |r| r.object_id() == object_id)
229    }
230
231    /// Returns true if any records were modified.
232    pub fn is_modified(&self) -> bool {
233        self.records.iter().any(|r| r.is_modified())
234    }
235
236    /// Adds a record to the container.
237    pub fn push(&mut self, record: BinaryRecord) {
238        self.records.push(record);
239    }
240
241    /// Removes a record by index.
242    pub fn remove(&mut self, index: usize) -> Option<BinaryRecord> {
243        if index < self.records.len() {
244            Some(self.records.remove(index))
245        } else {
246            None
247        }
248    }
249
250    /// Converts all records back to binary data.
251    pub fn to_binary(&self) -> Vec<Vec<u8>> {
252        self.records.iter().map(|r| r.to_binary()).collect()
253    }
254}
255
256impl IntoIterator for BinaryContainer {
257    type Item = BinaryRecord;
258    type IntoIter = std::vec::IntoIter<BinaryRecord>;
259
260    fn into_iter(self) -> Self::IntoIter {
261        self.records.into_iter()
262    }
263}
264
265impl<'a> IntoIterator for &'a BinaryContainer {
266    type Item = &'a BinaryRecord;
267    type IntoIter = std::slice::Iter<'a, BinaryRecord>;
268
269    fn into_iter(self) -> Self::IntoIter {
270        self.records.iter()
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use crate::api::cfb::Block;
278
279    #[test]
280    fn test_params_container_from_blocks() {
281        let blocks = vec![
282            Block {
283                offset: 0,
284                size: 20,
285                flags: 0,
286                data: b"|RECORD=1|NAME=A|\0".to_vec(),
287            },
288            Block {
289                offset: 24,
290                size: 20,
291                flags: 0,
292                data: b"|RECORD=2|NAME=B|\0".to_vec(),
293            },
294        ];
295
296        let container = ParamsContainer::from_blocks("/test", &blocks);
297        assert_eq!(container.len(), 2);
298        assert_eq!(container.get(0).unwrap().record_id(), Some(1));
299        assert_eq!(container.get(1).unwrap().record_id(), Some(2));
300    }
301
302    #[test]
303    fn test_filter_by_type() {
304        let params = vec![
305            ParameterCollection::from_string("|RECORD=1|"),
306            ParameterCollection::from_string("|RECORD=2|"),
307            ParameterCollection::from_string("|RECORD=1|"),
308        ];
309        let container = ParamsContainer::from_params_list("/test", params);
310
311        let record1s: Vec<_> = container.filter_by_type(1).collect();
312        assert_eq!(record1s.len(), 2);
313    }
314
315    #[test]
316    fn test_modification_tracking() {
317        let params = vec![ParameterCollection::from_string("|RECORD=1|NAME=Test|")];
318        let mut container = ParamsContainer::from_params_list("/test", params);
319
320        assert!(!container.is_modified());
321
322        container.get_mut(0).unwrap().set("NAME", "Modified");
323        assert!(container.is_modified());
324    }
325}