idf_parser/
lib.rs

1//! The crate parses [IDF (Intermediate Data Format)](https://en.wikipedia.org/wiki/Intermediate_Data_Format) files, specifically the IDF 3.0 format used for PCB design data exchange.
2//! It can parse board and panel .emn files, as well as library .emp files.
3//!
4//! [The IDF V3 specification.](http://www.simplifiedsolutionsinc.com/images/idf_v30_spec.pdf)
5//!
6//! # Example
7//! ```
8//! use idf_parser::parse_board_file;
9//! use idf_parser::parse_library_file;
10//!
11//! let board = parse_board_file("src/test_files/board.emn").unwrap();
12//! let panel = parse_board_file("src/test_files/panel.emn").unwrap();
13//! let library = parse_library_file("src/test_files/library.emp").unwrap();
14//! ```
15
16use crate::board::BoardPanel;
17use crate::library::Library;
18
19pub mod board;
20pub mod component_placement;
21pub mod components;
22pub mod drilled_holes;
23pub mod headers;
24pub mod library;
25pub mod notes;
26pub mod outlines;
27pub mod point;
28pub mod primitives;
29mod validation;
30
31/// Take in the path a board or panel .emn file and return a Board struct.
32pub fn parse_board_file(file_path: &str) -> Result<BoardPanel, String> {
33    if !file_path.ends_with(".emn") {
34        return Err("Board and panel files must end with .emn.".to_string());
35    }
36    let file = std::fs::read_to_string(file_path).map_err(|e| e.to_string())?;
37    let result = board::parse_board_or_panel(&file);
38
39    match result {
40        Ok(board) => Ok(board),
41        Err(e) => Err(format!("Failed to parse board file: {}", e)),
42    }
43}
44
45/// Take in the path a library .emp file and return a Library struct.
46pub fn parse_library_file(file_path: &str) -> Result<Library, String> {
47    if !file_path.ends_with(".emp") {
48        return Err("Library files must end with .emp.".to_string());
49    }
50
51    let file = std::fs::read_to_string(file_path).map_err(|e| e.to_string())?;
52    let result = library::parse_library(&file);
53
54    match result {
55        Ok(library) => Ok(library),
56        Err(e) => Err(format!("Failed to parse library file: {}", e)),
57    }
58}
59
60/// Parse an optional panel file, library file, and 1 or more board files and validate them.
61///
62/// An assembly is either a single board and a library file, or a panel file,
63/// 1 or more board files and a library file.
64/// Here we parse all the provided files and check that all board and component references are valid.
65fn parse_assembly(
66    panel_file: Option<&str>,
67    library_file: &str,
68    board_files: Vec<&str>,
69) -> Result<(Option<BoardPanel>, Library, Vec<BoardPanel>), String> {
70    let mut boards = Vec::new();
71
72    let panel = match panel_file {
73        Some(file) => Some(parse_board_file(file)?),
74        None => None,
75    };
76
77    let library = parse_library_file(library_file)?;
78
79    for board_file in board_files {
80        boards.push(parse_board_file(board_file)?);
81    }
82
83    for board in &boards {
84        validation::library_references_valid(&library, board)?;
85    }
86
87    if let Some(panel) = &panel {
88        validation::panel_references_valid(panel, &boards)?;
89    }
90
91    Ok((panel, library, boards))
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_parse_spec_board_file() {
100        parse_board_file("src/test_files/board.emn").unwrap();
101    }
102    #[test]
103    fn test_parse_isol_board_file() {
104        parse_board_file("src/test_files/ISOL.emn").unwrap();
105    }
106
107    #[test]
108    fn test_parse_beaglebone_board_file() {
109        parse_board_file("src/test_files/beaglebone.emn").unwrap();
110    }
111
112    #[test]
113    fn test_parse_ain_board_file() {
114        parse_board_file("src/test_files/ain.emn").unwrap();
115    }
116
117    #[test]
118    fn test_parse_esp_board_file() {
119        parse_board_file("src/test_files/esp.emn").unwrap();
120    }
121
122    #[test]
123    fn test_parse_library_file() {
124        parse_library_file("src/test_files/library.emp").unwrap();
125    }
126    #[test]
127    fn test_parse_isol_library_file() {
128        parse_library_file("src/test_files/ISOL.emp").unwrap();
129    }
130
131    #[test]
132    fn test_parse_beaglebone_library_file() {
133        parse_library_file("src/test_files/beaglebone.emp").unwrap();
134    }
135
136    #[test]
137    fn test_parse_ain_library_file() {
138        parse_library_file("src/test_files/ain.emp").unwrap();
139    }
140
141    #[test]
142    fn test_parse_esp_library_file() {
143        parse_library_file("src/test_files/esp.emp").unwrap();
144    }
145
146    #[test]
147    fn test_parse_assembly() {
148        let panel = Some("src/test_files/panel.emn");
149        let library = "src/test_files/library.emp";
150        let boards = vec!["src/test_files/board.emn"];
151
152        let result = parse_assembly(panel, library, boards.clone());
153        assert!(result.is_ok());
154
155        // This panel file references a board that doesn't exist
156        let invalid_panel = Some("src/test_files/invalid_panel.emn");
157
158        let result = parse_assembly(invalid_panel, library, boards);
159        assert!(result.is_err());
160    }
161}