kicad_parse_gen/
lib.rs

1// (c) 2016-2017 Productize SPRL <joost@productize.be>
2
3//! Kicad file format parser and generator library
4#![warn(missing_docs)]
5
6#[macro_use]
7extern crate log;
8extern crate shellexpand;
9extern crate symbolic_expressions;
10// extern crate strum;
11// #[macro_use]
12// extern crate strum_macros;
13
14use std::fmt;
15use std::path::{Path, PathBuf};
16use std::result;
17
18pub use symbolic_expressions::{Sexp,SexpError};
19pub use error::*;
20
21/// read file utility wrapper function
22pub use util::read_file;
23/// write file utility wrapper function
24pub use util::write_file;
25
26fn parse_split_quote_aware_int(n_opt: Option<usize>, s: &str) -> Result<Vec<String>, KicadError> {
27    let mut i = 0;
28    let mut v: Vec<String> = vec![];
29    // if we are inside our outside a quoted string
30    let mut inside_quotation = false;
31    let mut quotation_seen = false;
32    let mut s2: String = "".into();
33    for c in s.chars() {
34        if let Some(n) = n_opt {
35            if i == n {
36                // if we're in the nth. just keep collecting in current string
37                s2.push(c);
38                continue;
39            }
40        }
41        // detection of starting "
42        if !inside_quotation && c == '\"' {
43            inside_quotation = true;
44            continue;
45        }
46        // detection of final "
47        if inside_quotation && c == '\"' {
48            inside_quotation = false;
49            quotation_seen = true;
50            continue;
51        }
52        // detection of space before or in between parts
53        if !inside_quotation && c == ' ' {
54            if !s2.is_empty() || quotation_seen {
55                i += 1;
56                v.push(s2.clone());
57                s2.clear();
58            }
59            quotation_seen = false;
60            continue;
61        }
62        s2.push(c);
63    }
64    if !s2.is_empty() || quotation_seen {
65        v.push(s2.clone())
66    }
67    if let Some(n) = n_opt {
68        if v.len() < n {
69            return str_error(format!("expecting {} elements in {}", n, s));
70        }
71    }
72    Ok(v)
73}
74
75fn parse_split_quote_aware_n(n: usize, s: &str) -> Result<Vec<String>, KicadError> {
76    parse_split_quote_aware_int(Some(n), s)
77}
78fn parse_split_quote_aware(s: &str) -> Result<Vec<String>, KicadError> {
79    parse_split_quote_aware_int(None, s)
80}
81
82/// types of Kicad files that can be found
83#[derive(Debug)]
84pub enum KicadFile {
85    /// unknown file, probably no kicad file
86    Unknown(PathBuf),
87    /// a Kicad module, also know as a footprint
88    Module(footprint::Module),
89    /// a Kicad schematic file
90    Schematic(schematic::Schematic),
91    /// a Kicad layout file
92    Layout(layout::Layout),
93    /// a Kicad symbol library file
94    SymbolLib(symbol_lib::SymbolLib),
95    /// a Kicad project file
96    Project(project::Project),
97    /// a Kicad fp-lib-table file
98    FpLibTable(fp_lib_table::FpLibTable),
99}
100
101/// types of Kicad files that we expect to read
102#[derive(PartialEq)]
103pub enum Expected {
104    /// a Kicad module, also know as a footprint
105    Module,
106    /// a Kicad schematic file
107    Schematic,
108    /// a Kicad layout file
109    Layout,
110    /// a Kicad symbol library file
111    SymbolLib,
112    /// a Kicad project file
113    Project,
114    /// an fp-lib-table file
115    FpLibTable,
116    /// any Kicad file
117    Any,
118}
119
120
121impl fmt::Display for KicadFile {
122    fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
123        match *self {
124            KicadFile::Unknown(ref x) => write!(f, "unknown: {}", x.to_str().unwrap()),
125            KicadFile::Module(_) => write!(f, "module"),
126            KicadFile::Schematic(_) => write!(f, "schematic"),
127            KicadFile::Layout(_) => write!(f, "layout"),
128            KicadFile::SymbolLib(_) => write!(f, "symbollib"),
129            KicadFile::Project(_) => write!(f, "project"),
130            KicadFile::FpLibTable(_) => write!(f, "fp-lib-table"),
131        }
132    }
133}
134
135/// try to read a file, trying to parse it as the different formats
136/// and matching it against the Expected type
137#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
138pub fn read_kicad_file(name: &Path, expected: Expected) -> Result<KicadFile, KicadError> {
139    let data = read_file(name)?;
140    match footprint::parse(&data) {
141        Ok(module) => return Ok(KicadFile::Module(module)),
142        Err(x) => if expected == Expected::Module {
143            return Err(x);
144        },
145    }
146    match schematic::parse(Some(PathBuf::from(name)), &data) {
147        Ok(sch) => return Ok(KicadFile::Schematic(sch)),
148        Err(x) => if expected == Expected::Schematic {
149            return Err(x);
150        },
151    }
152    match layout::parse(&data) {
153        Ok(layout) => return Ok(KicadFile::Layout(layout)),
154        Err(x) => if expected == Expected::Layout {
155            return Err(x);
156        },
157    }
158    match symbol_lib::parse_str(&data) {
159        Ok(sl) => return Ok(KicadFile::SymbolLib(sl)),
160        Err(x) => if expected == Expected::SymbolLib {
161            return Err(x);
162        },
163    }
164    match project::parse_str(&data) {
165        Ok(p) => return Ok(KicadFile::Project(p)),
166        Err(x) => if expected == Expected::Project {
167            return Err(x);
168        },
169    }
170    match fp_lib_table::parse(&data) {
171        Ok(p) => return Ok(KicadFile::FpLibTable(p)),
172        Err(x) => if expected == Expected::FpLibTable {
173            return Err(x.into());
174        },
175    }
176    Ok(KicadFile::Unknown(PathBuf::from(name)))
177}
178
179/// read a file, expecting it to be a Kicad module file
180pub fn read_module(name: &Path) -> Result<footprint::Module, KicadError> {
181    match read_kicad_file(name, Expected::Module)? {
182        KicadFile::Module(mo) => Ok(mo),
183        x => str_error(format!("unexpected {} in {}", x, name.display())),
184    }
185}
186
187/// read a file, expecting it to be a Kicad schematic
188pub fn read_schematic(name: &Path) -> Result<schematic::Schematic, KicadError> {
189    match read_kicad_file(name, Expected::Schematic)? {
190        KicadFile::Schematic(mo) => Ok(mo),
191        x => str_error(format!("unexpected {} in {}", x, name.display())),
192    }
193}
194
195/// read a file, expecting it to be a Kicad layout file
196pub fn read_layout(name: &Path) -> Result<layout::Layout, KicadError> {
197    match read_kicad_file(name, Expected::Layout)? {
198        KicadFile::Layout(mo) => Ok(mo),
199        x => str_error(format!("unexpected {} in {}", x, name.display())),
200    }
201}
202
203/// write out a kicad Layout to a file
204pub fn write_layout(layout: &layout::Layout, name: &Path) -> Result<(), KicadError> {
205    let s = layout::layout_to_string(layout, 0)?;
206    write_file(name, &s)
207}
208
209/// write out a kicad `Module` to a file
210pub fn write_module(module: &footprint::Module, name: &Path) -> Result<(), KicadError> {
211    let s = footprint::module_to_string(module, 0)?;
212    write_file(&name, &s)
213}
214
215/// read a file, expecting it to be a Kicad symbol library file
216pub fn read_symbol_lib(name: &Path) -> Result<symbol_lib::SymbolLib, KicadError> {
217    match read_kicad_file(name, Expected::SymbolLib)? {
218        KicadFile::SymbolLib(mo) => Ok(mo),
219        x => str_error(format!("unexpected {} in {}", x, name.display())),
220    }
221}
222
223/// read a file, expecting it to be a Kicad project file
224pub fn read_project(name: &Path) -> Result<project::Project, KicadError> {
225    match read_kicad_file(name, Expected::Project)? {
226        KicadFile::Project(mo) => Ok(mo),
227        x => str_error(format!("unexpected {} in {}", x, name.display())),
228    }
229}
230
231/// read a file, expecting it to be an fp-lib-table
232pub fn read_fp_lib_table(name: &Path) -> Result<fp_lib_table::FpLibTable, KicadError> {
233    match read_kicad_file(name, Expected::FpLibTable)? {
234        KicadFile::FpLibTable(mo) => Ok(mo),
235        x => str_error(format!("unexpected {} in {}", x, name.display())),
236    }
237}
238
239fn wrap<X, Y, F, G>(s: &Sexp, make: F, wrapper: G) -> result::Result<Y,SexpError>
240where
241    F: Fn(&Sexp) -> result::Result<X,SexpError>,
242    G: Fn(X) -> Y,
243{
244    Ok(wrapper(make(s)?))
245}
246
247#[derive(Debug)]
248/// A bounding box
249pub struct Bound {
250    /// smaller x
251    pub x1: f64,
252    /// smaller y
253    pub y1: f64,
254    /// bigger x
255    pub x2: f64,
256    /// bigger y
257    pub y2: f64,
258    /// item is bounded
259    pub is_bounded: bool,
260}
261
262impl Default for Bound {
263    fn default() -> Bound {
264        Bound {
265            x1: 0.0,
266            y1: 0.0,
267            x2: 0.0,
268            y2: 0.0,
269            is_bounded: false,
270        }
271    }
272}
273
274impl Bound {
275    /// create a new bound
276    pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Bound {
277        Bound {
278            x1: x1,
279            y1: y1,
280            x2: x2,
281            y2: y2,
282            is_bounded: true,
283        }
284    }
285    /// create a new bound
286    pub fn new_from_i64(x1: i64, y1: i64, x2: i64, y2: i64) -> Bound {
287        Bound {
288            x1: x1 as f64,
289            y1: y1 as f64,
290            x2: x2 as f64,
291            y2: y2 as f64,
292            is_bounded: true,
293        }
294    }
295
296    /// update the bound with another one
297    pub fn update(&mut self, other: &Bound) {
298        if other.is_bounded {
299            if !self.is_bounded {
300                self.is_bounded = true;
301                self.x1 = other.x1;
302                self.y1 = other.y1;
303                self.x2 = other.x2;
304                self.y2 = other.y2;
305            } else {
306                self.x1 = self.x1.min(other.x1);
307                self.y1 = self.y1.min(other.y1);
308                self.x2 = self.x2.max(other.x2);
309                self.y2 = self.y2.max(other.y2);
310            }
311        }
312    }
313
314    /// call this when you constructed a default bound and potentionally had zero updates
315    pub fn swap_if_needed(&mut self) {
316        if self.x1 > self.x2 {
317            std::mem::swap(&mut self.x1, &mut self.x2);
318        }
319        if self.y1 > self.y2 {
320            std::mem::swap(&mut self.y1, &mut self.y2);
321        }
322    }
323
324    /// calculate the width of the `Bound`
325    pub fn width(&self) -> f64 {
326        (self.x1 - self.x2).abs()
327    }
328
329    /// calculate the height of the `Bound`
330    pub fn height(&self) -> f64 {
331        (self.y1 - self.y2).abs()
332    }
333}
334
335/// calculate the bounding box of a layout item
336pub trait BoundingBox {
337    /// calculate the bounding box of a layout item
338    fn bounding_box(&self) -> Bound;
339}
340
341/// item location can be adjusted
342pub trait Adjust {
343    /// adjust the location of the item
344    fn adjust(&mut self, x: f64, y: f64);
345}
346
347/// ordering used for ordering by component reference
348/// to e.g. avoid U1 U101 U2 and get U1 U2 U101
349pub fn reference_ord(r: &str) -> (char, i64) {
350    let c = r.chars().nth(0).unwrap();
351    let mut s = String::new();
352    for c in r.chars() {
353        if c >= '0' && c <= '9' {
354            s.push(c)
355        }
356        // if the &str contains a list of references,
357        // only take 1st element
358        if c == ',' {
359            break;
360        }
361    }
362    let num = match s.parse::<i64>() {
363        Ok(n) => n,
364        Err(_) => 0,
365    };
366    (c, num)
367}
368
369
370/// Kicad error handling code and types
371pub mod error;
372/// Kicad footprint format handling
373pub mod footprint;
374/// Kicad schematic format handling
375pub mod schematic;
376/// Kicad layout format handling
377pub mod layout;
378/// Kicad symbol library format handling
379pub mod symbol_lib;
380/// Kicad project format handling
381pub mod project;
382/// Kicad fp-lib-table format handling
383pub mod fp_lib_table;
384/// checking and fixing related to the Kicad Library Convention
385pub mod checkfix;
386
387mod util;
388mod formatter;