kicad_parse_gen/
checkfix.rs

1// (c) 2017 Productize SPRL <joost@productize.be>
2
3use std::fmt;
4use std::result;
5
6/// symbol settings for checking and fixing
7pub struct SymConfig {}
8
9/// module settings for checking and fixing
10pub struct ModConfig {
11    /// font size
12    pub font_size: f64,
13    /// font thickness
14    pub font_thickness: f64,
15}
16
17/// config settings for checking and fixing
18pub struct Config {
19    /// name of the Config
20    pub name: String,
21    /// symbol configuration
22    pub s: SymConfig,
23    /// module configuration
24    pub m: ModConfig,
25}
26
27impl Config {
28    /// create a `Config` based on the Kicad KLC
29    pub fn klc() -> Config {
30        Config {
31            name: "KLC 2.0.10".into(),
32            s: SymConfig {},
33            m: ModConfig {
34                font_size: 1.0,
35                font_thickness: 0.15,
36            },
37        }
38    }
39}
40
41/// Check & Fix trait to be implemented for KLC checking and fixing
42pub trait CheckFix {
43    /// check an item against the KLC
44    fn check(&self, config: &Config) -> Vec<CheckFixData>;
45
46    /// fix up an item against the KLC
47    fn fix(&mut self, _config: &Config) {}
48}
49
50#[derive(Debug)]
51/// a KLC check result data
52pub enum CheckFixData {
53    /// a KLC check result item
54    Item(CheckFixItem),
55    /// a list of more KLC check result datas
56    More(Vec<CheckFixData>),
57}
58
59impl CheckFixData {
60    /// if a `CheckFixData` is of type `More` but contains only one item, change it into an `Item`
61    pub fn flatter(self) -> Self {
62        match self {
63            CheckFixData::Item(_) => self,
64            CheckFixData::More(v) => if v.len() == 1 {
65                v.into_iter().next().unwrap()
66            } else {
67                CheckFixData::More(v)
68            },
69        }
70    }
71
72    /// create a new `CheckFixData`
73    pub fn new<A: fmt::Display, B: Into<String>>(
74        section: i64,
75        rule: i64,
76        item: A,
77        message: B,
78    ) -> Self {
79        let i = CheckFixItem::new(section, rule, item, message.into());
80        CheckFixData::Item(i)
81    }
82
83    /// create a new informational `CheckFixData`
84    pub fn info<A: fmt::Display, B: Into<String>>(
85        section: i64,
86        rule: i64,
87        item: A,
88        message: B,
89    ) -> Self {
90        let i = CheckFixItem::new(section, rule, item, message.into()).info();
91        CheckFixData::Item(i)
92    }
93
94    /// dump a `CheckFixData` on the `Log` logger
95    pub fn dump_on_logger(&self, indent: usize) {
96        match *self {
97            CheckFixData::Item(ref item) => item.dump_on_logger(indent),
98            CheckFixData::More(ref more) => for klcdata in more {
99                let new_indent = match *klcdata {
100                    CheckFixData::Item(_) => indent,
101                    CheckFixData::More(_) => indent + 1,
102                };
103                klcdata.dump_on_logger(new_indent)
104            },
105        }
106    }
107}
108
109#[derive(Debug)]
110/// a KLC check result item
111pub struct CheckFixItem {
112    /// KLC section
113    pub section: i64,
114    /// KLC rule in the section
115    pub rule: i64,
116    /// item that this is about
117    pub item: String,
118    /// message about the problem
119    pub message: String,
120    /// if the item is informational only
121    pub info: bool,
122}
123impl CheckFixItem {
124    /// create a new `CheckFixItem`
125    pub fn new<A: fmt::Display, B: Into<String>>(
126        section: i64,
127        rule: i64,
128        item: A,
129        message: B,
130    ) -> Self {
131        CheckFixItem {
132            section: section,
133            rule: rule,
134            item: format!("{}", item),
135            message: message.into(),
136            info: false,
137        }
138    }
139
140    /// create a new informational `CheckFixItem`
141    pub fn info(self) -> CheckFixItem {
142        CheckFixItem { info: true, ..self }
143    }
144
145    /// dump a `CheckFixItem` on a `Log` logger
146    pub fn dump_on_logger(&self, indent: usize) {
147        let indent = ::std::iter::repeat(" ")
148            .take(indent * 2)
149            .collect::<String>();
150        if self.info {
151            info!(
152                "{}{}.{} {}:{}",
153                indent,
154                self.section,
155                self.rule,
156                self.item,
157                self.message
158            )
159        } else {
160            warn!(
161                "{}{}.{} {}:{}",
162                indent,
163                self.section,
164                self.rule,
165                self.item,
166                self.message
167            )
168        }
169    }
170}
171
172#[derive(Debug)]
173/// KLC Section
174pub enum KLCSection {
175    /// General
176    General,
177    /// Symbol Library Names
178    SymbolLibraryNames,
179    /// Symbol Names
180    SymbolNames,
181    /// Symbol Rules
182    SymbolRules,
183    /// Footprint Library Names
184    FootprintLibraryNames,
185    /// Footprint Names
186    FootprintNames,
187    /// Footprint Rules
188    FootprintRules,
189    /// SMD Rules
190    SMDRules,
191    /// THT Rules
192    THTRules,
193    /// Footprint Properties
194    FootprintProperties,
195}
196
197impl Into<i64> for KLCSection {
198    fn into(self) -> i64 {
199        match self {
200            KLCSection::General => 1,
201            KLCSection::SymbolLibraryNames => 2,
202            KLCSection::SymbolNames => 3,
203            KLCSection::SymbolRules => 4,
204            KLCSection::FootprintLibraryNames => 5,
205            KLCSection::FootprintNames => 6,
206            KLCSection::FootprintRules => 7,
207            KLCSection::SMDRules => 8,
208            KLCSection::THTRules => 9,
209            KLCSection::FootprintProperties => 10,
210        }
211    }
212}
213
214impl fmt::Display for KLCSection {
215    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
216        let s = match *self {
217            KLCSection::General => "General Rules",
218            KLCSection::SymbolLibraryNames => "Symbol Library Names",
219            KLCSection::SymbolNames => "Symbol Names",
220            KLCSection::SymbolRules => "General Rules for Symbols",
221            KLCSection::FootprintLibraryNames => "Footprint Library Names",
222            KLCSection::FootprintNames => "Footprint Names",
223            KLCSection::FootprintRules => "General Rules for Footprints",
224            KLCSection::SMDRules => "Rules for SMD Footprints",
225            KLCSection::THTRules => "Rules for Through-hole Footprints",
226            KLCSection::FootprintProperties => "Footprint Properties",
227        };
228        write!(f, "{}", s)
229    }
230}
231
232/* 
233
2341.7 valid characters in string
235
236Filenames, symbol names, footprint names and model names must contain only valid characters, as below:
237* Alphanumeric characters (A-Z, a-z, 0-9)
238* Underscore _
239* Hyphen / dash -
240* Period / dot .
241
242*/
243
244
245const ALLOWED_1_7: &'static str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.";
246
247fn allowed_1_7(s: &str) -> Option<Vec<String>> {
248    let mut v = vec![];
249    for (i, c) in s.chars().enumerate() {
250        if ALLOWED_1_7.chars().find(|&x| x == c).is_none() {
251            v.push(format!("Invalid char '{}' at {} in '{}'", c, i, s))
252        }
253    }
254    if v.is_empty() {
255        None
256    } else {
257        Some(v)
258    }
259}
260
261/// check if a name is allowed according to KLC 1.7
262pub fn allowed_1_7_items(s: &str) -> Vec<CheckFixData> {
263    let mut v = vec![];
264    if let Some(v2) = allowed_1_7(s) {
265        for x in v2 {
266            v.push(CheckFixData::new(1, 7, s, x))
267        }
268    }
269    v
270}
271
272/// check if a name is allowed according to KLC 1.7
273pub fn is_allowed_1_7(s: &str) -> bool {
274    allowed_1_7(s).is_none()
275}
276
277/*
278
2794.3 Pin stacking. Placing pins in the same position results in the circuits being connected. Pins may be placed in the same location under certain circumstances:
280* Pins must not be of type No Connect
281* Pins are logically connected in the symbol
282* Pins must have the same name
283* Pins must have the same electrical type
284* Only one pin must be visible (all others set to invisible)
285* Stacks of type Output, Power Output and Power Input are special cases. One visible pin must have the correct type, and all other pins in the stack must be passive and invisible.
286
287*/
288
289/* 
290
2914.4 Pins should be grouped logically, rather than physically
292* Pin location should not necessarily follow footprint pinout
293* Pins with similar functions should be placed together, e.g. SPI_MISO, SPI_MOSI, SPI_SCK, SPI_CS and UART_TX, UART_RX
294* Ports should be ordered from top to bottom, unless this conflicts with the above requirements
295
296 */
297
298/* 
299
3004.5 Whenever possible, pins should be arranged by function:
301* Positive Power pins should be placed at top of the symbol, e.g. Vcc, Vdd, Vin, V+, etc
302* Negative Power and Ground pins should be placed at the bottom of the symbol, e.g. GND, Vss, V-, etc
303* Input/Control/Logic pins should be placed on the left of the symbol, e.g. opamp +/-, NPN base, SPI pins on an DAC, transformer primary, UART Tx/Rx pins, etc.
304* Output/Controlled/Driver pins should be placed on the right of the symbol, e.g. opamp output, DAC output, transformer secondary, RS232 Tx/Rx, etc. 
305
306 */
307
308/*
309
3104.6 Pin Electrical type should be set to match the appropriate pin function
311* Power and Ground pins should be set to either POWER INPUT or POWER OUTPUT
312* Other pin types should be set as appropriate 
313
314 */
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319    #[test]
320    fn test_allowed_1_7_1() {
321        assert!(allowed_1_7("Hello_world_1.23-4").is_none())
322    }
323
324    #[test]
325    fn test_allowed_1_7_2() {
326        let t = allowed_1_7("Hello world")
327            .unwrap()
328            .into_iter()
329            .next()
330            .unwrap();
331        assert_eq!(t, "Invalid char ' ' at 5 in 'Hello world'")
332    }
333}