altium/prj/
prjcfg.rs

1#![allow(clippy::unnecessary_wraps)]
2#![allow(clippy::needless_pass_by_value)]
3
4use std::borrow::ToOwned;
5use std::collections::HashMap;
6use std::fmt::Debug;
7use std::path::Path;
8
9use ini::{Ini, Properties};
10use lazy_static::lazy_static;
11use regex::Regex;
12use uuid::Uuid;
13
14use super::parse::{parse_bool, parse_int, parse_string, parse_unique_id};
15use crate::common::UniqueId;
16use crate::error::ErrorKind;
17use crate::Error;
18
19lazy_static! {
20    /// `Document1`, `Document2`, etc
21    static ref DOC_RE: Regex = Regex::new(r"Document\d+").unwrap();
22}
23
24/// Representation of a PCB Project file (`.PrjPcb`)
25#[non_exhaustive]
26pub struct PrjPcb {
27    design: Design,
28    preferences: Option<Preferences>,
29    release: Option<Release>,
30    documents: Vec<Document>,
31    variants: Vec<Variant>,
32    parameters: Vec<HashMap<String, String>>,
33    configurations: Vec<Configuration>,
34    original: Ini,
35}
36
37impl PrjPcb {
38    fn design(&self) -> &Design {
39        &self.design
40    }
41
42    fn preferences(&self) -> Option<&Preferences> {
43        self.preferences.as_ref()
44    }
45
46    fn release(&self) -> Option<&Release> {
47        self.release.as_ref()
48    }
49
50    /// List all documents that are members of this project
51    pub fn documents(&self) -> &[Document] {
52        &self.documents
53    }
54
55    /// Open a `.PrjPcb` file
56    pub fn from_file<P: AsRef<Path>>(filename: P) -> Result<Self, Error> {
57        let ini = Ini::load_from_file(filename)?;
58        Self::from_ini(ini)
59    }
60
61    /// Create this type from a string
62    pub fn from_string(s: &str) -> Result<Self, Error> {
63        let ini = Ini::load_from_str(s)?;
64        Self::from_ini(ini)
65    }
66
67    fn from_ini(ini: Ini) -> Result<Self, Error> {
68        for (i, s) in ini.iter().take(10).enumerate() {
69            eprintln!("{i}:\n{s:#?}\n");
70        }
71
72        Ok(Self {
73            design: Design::from_prj_ini(ini)?,
74            preferences: todo!(),
75            release: todo!(),
76            documents: todo!(),
77            variants: todo!(),
78            parameters: todo!(),
79            configurations: todo!(),
80            original: todo!(),
81        })
82    }
83}
84
85impl Debug for PrjPcb {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        f.debug_struct("PrjPcb")
88            .field("design", &self.design)
89            .field("preferences", &self.preferences)
90            .field("release", &self.release)
91            .field("documents", &self.documents)
92            .field("variants", &self.variants)
93            .field("parameters", &self.parameters)
94            .field("configurations", &self.configurations)
95            .finish_non_exhaustive()
96    }
97}
98
99/// Design section of a `PrjPCB` file
100#[non_exhaustive]
101#[derive(Debug, PartialEq)]
102pub struct Design {
103    original: Properties,
104}
105
106impl Design {
107    fn from_prj_ini(ini: Ini) -> Result<Self, ErrorKind> {
108        let sec = ini
109            .section(Some("Design"))
110            .ok_or(ErrorKind::MissingSection("Design".to_owned()))?;
111
112        Ok(Self {
113            original: sec.clone(),
114        })
115    }
116
117    fn to_ini(&self) -> &Properties {
118        &self.original
119    }
120}
121
122#[non_exhaustive]
123#[derive(Debug, PartialEq)]
124pub struct Preferences {
125    original: Properties,
126}
127
128impl Preferences {
129    fn from_prj_ini(ini: Ini) -> Result<Option<Self>, ErrorKind> {
130        Ok(ini.section(Some("Preferences")).map(|sec| Self {
131            original: sec.clone(),
132        }))
133    }
134}
135
136#[non_exhaustive]
137#[derive(Debug, PartialEq)]
138pub struct Release {
139    original: Properties,
140}
141
142impl Release {
143    fn from_prj_ini(ini: &Ini) -> Result<Self, ErrorKind> {
144        todo!()
145    }
146}
147
148/// A single document located in a `.PrjPcb` file
149#[non_exhaustive]
150#[derive(Debug, PartialEq)]
151pub struct Document {
152    document_path: String,
153    annotation_en: bool,
154    annotation_start_value: i32,
155    annotation_idx_ctrl_en: bool,
156    annotation_suffix: String,
157    annotate_order: i32,
158    do_libarary_update: bool,
159    do_database_update: bool,
160    class_gen_cc_auto_en: bool,
161    class_gen_cc_auto_room_en: bool,
162    class_gen_nc_auto_scope: String,
163    item_revision_guid: String,
164    generate_class_cluster: bool,
165    document_unique_id: UniqueId,
166}
167
168impl Document {
169    /// The path to this document
170    pub fn path(&self) -> &str {
171        &self.document_path
172    }
173
174    /// This document's unique ID
175    pub fn unique_id(&self) -> UniqueId {
176        self.document_unique_id
177    }
178
179    /// Create a vector of `Document`s from an ini file
180    fn from_prj_ini(ini: &Ini) -> Result<Vec<Self>, Error> {
181        let mut doc_sections: Vec<&str> = ini
182            .sections()
183            .filter_map(|nameopt| {
184                nameopt.and_then(|name| {
185                    if DOC_RE.is_match(name) {
186                        Some(name)
187                    } else {
188                        None
189                    }
190                })
191            })
192            .collect();
193
194        doc_sections.sort_by_key(|s| s.strip_prefix("Document").unwrap().parse::<i32>().unwrap());
195
196        let mut ret = Vec::new();
197        let sec_iter = doc_sections
198            .iter()
199            .map(|sec_name| ini.section(Some(*sec_name)).unwrap())
200            .map(Self::from_section);
201
202        for sec_opt in sec_iter {
203            ret.push(sec_opt?);
204        }
205
206        Ok(ret)
207    }
208
209    /// Create a single `Document` from an ini section
210    fn from_section(sec: &Properties) -> Result<Self, Error> {
211        Ok(Self {
212            document_path: parse_string(sec, "DocumentPath"),
213            annotation_en: parse_bool(sec, "AnnotationEnabled"),
214            annotation_start_value: parse_int(sec, "AnnotateStartValue"),
215            annotation_idx_ctrl_en: parse_bool(sec, "AnnotationIndexControlEnabled"),
216            annotation_suffix: parse_string(sec, "AnnotateSuffix"),
217            annotate_order: parse_int(sec, "AnnotateScope"),
218            do_libarary_update: parse_bool(sec, "DoLibraryUpdate"),
219            do_database_update: parse_bool(sec, "DoDatabaseUpdate"),
220            class_gen_cc_auto_en: parse_bool(sec, "ClassGenCCAutoEnabled"),
221            class_gen_cc_auto_room_en: parse_bool(sec, "ClassGenCCAutoRoomEnabled"),
222            class_gen_nc_auto_scope: parse_string(sec, "ClassGenNCAutoScope"),
223            item_revision_guid: parse_string(sec, "DItemRevisionGUID"),
224            generate_class_cluster: parse_bool(sec, "GenerateClassCluster"),
225            document_unique_id: parse_unique_id(sec, "DocumentUniqueId")?,
226        })
227    }
228}
229
230#[non_exhaustive]
231#[derive(Debug, PartialEq)]
232pub struct Variant {
233    unique_id: Uuid,
234    description: String,
235    allow_fabrication: bool,
236    parameter_count: u32,
237    variations: Vec<Variation>,
238}
239
240impl Variant {
241    fn from_prj_ini(ini: &Ini) -> Result<Self, ErrorKind> {
242        todo!()
243    }
244}
245
246#[non_exhaustive]
247#[derive(Debug, PartialEq)]
248pub struct Variation {}
249
250impl Variation {
251    fn from_prj_ini(ini: &Ini) -> Result<Self, ErrorKind> {
252        todo!()
253    }
254}
255
256#[non_exhaustive]
257#[derive(Debug, PartialEq)]
258pub struct Configuration {
259    name: String,
260    // ConfigurationType should be an enum, seems to match with `ContentTypeGUID`
261}
262
263impl Configuration {
264    fn from_prj_ini(ini: &Ini) -> Result<Self, ErrorKind> {
265        todo!()
266    }
267}