1#![warn(missing_docs)]
5
6#[macro_use]
7extern crate log;
8extern crate shellexpand;
9extern crate symbolic_expressions;
10use std::fmt;
15use std::path::{Path, PathBuf};
16use std::result;
17
18pub use symbolic_expressions::{Sexp,SexpError};
19pub use error::*;
20
21pub use util::read_file;
23pub 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 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 s2.push(c);
38 continue;
39 }
40 }
41 if !inside_quotation && c == '\"' {
43 inside_quotation = true;
44 continue;
45 }
46 if inside_quotation && c == '\"' {
48 inside_quotation = false;
49 quotation_seen = true;
50 continue;
51 }
52 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#[derive(Debug)]
84pub enum KicadFile {
85 Unknown(PathBuf),
87 Module(footprint::Module),
89 Schematic(schematic::Schematic),
91 Layout(layout::Layout),
93 SymbolLib(symbol_lib::SymbolLib),
95 Project(project::Project),
97 FpLibTable(fp_lib_table::FpLibTable),
99}
100
101#[derive(PartialEq)]
103pub enum Expected {
104 Module,
106 Schematic,
108 Layout,
110 SymbolLib,
112 Project,
114 FpLibTable,
116 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#[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
179pub 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
187pub 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
195pub 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
203pub 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
209pub 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
215pub 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
223pub 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
231pub 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)]
248pub struct Bound {
250 pub x1: f64,
252 pub y1: f64,
254 pub x2: f64,
256 pub y2: f64,
258 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 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 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 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 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 pub fn width(&self) -> f64 {
326 (self.x1 - self.x2).abs()
327 }
328
329 pub fn height(&self) -> f64 {
331 (self.y1 - self.y2).abs()
332 }
333}
334
335pub trait BoundingBox {
337 fn bounding_box(&self) -> Bound;
339}
340
341pub trait Adjust {
343 fn adjust(&mut self, x: f64, y: f64);
345}
346
347pub 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 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
370pub mod error;
372pub mod footprint;
374pub mod schematic;
376pub mod layout;
378pub mod symbol_lib;
380pub mod project;
382pub mod fp_lib_table;
384pub mod checkfix;
386
387mod util;
388mod formatter;