Skip to main content

cmsis_pdsc_parser/
examples.rs

1//! Contains the types required to represent a [PDSC Examples](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_examples) element
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
6/// Represents the [PDSC examples](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_examples) element
7///
8/// Groups all example projects published by a pack.
9pub struct Examples {
10    /// Example project definitions (1..*)
11    #[serde(rename = "example", default)]
12    pub examples: Vec<Example>,
13}
14
15#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
16/// Represents a [PDSC example](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_example) element
17///
18/// Defines a single example project, its boards, tool environments, and classification attributes.
19pub struct Example {
20    /// Short example identifier
21    pub name: String,
22
23    /// Path to the example folder relative to the pack root
24    pub folder: String,
25
26    /// Archive filename containing the example files
27    pub archive: Option<String>,
28
29    /// Path to the example documentation file
30    pub doc: String,
31
32    /// Example version
33    pub version: Option<String>,
34
35    /// Publishing permission; default `true`
36    pub public: Option<bool>,
37
38    /// Brief description of the example
39    pub description: String,
40
41    /// Target boards for this example (0..*)
42    #[serde(rename = "board", default)]
43    pub boards: Vec<ExampleBoard>,
44
45    /// IDE/tool project files for this example
46    pub project: ExampleProject,
47
48    /// Classification attributes for discovery and filtering
49    pub attributes: Option<ExampleAttributes>,
50}
51
52#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
53/// Represents an [example board reference](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_example_board)
54///
55/// Identifies a development board on which the example has been tested.
56pub struct ExampleBoard {
57    /// Board vendor name
58    pub vendor: String,
59
60    /// Commercial board name
61    pub name: String,
62
63    /// Board revision
64    pub revision: Option<String>,
65
66    /// Device vendor (deprecated since v1.1; prefer board's mounted device)
67    #[serde(rename = "Dvendor")]
68    pub device_vendor: Option<String>,
69
70    /// Device name (deprecated since v1.1)
71    #[serde(rename = "Dname")]
72    pub device_name: Option<String>,
73}
74
75#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
76/// Represents the [example project](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_example_project) element
77///
78/// Groups the tool-specific environment entries for this example.
79pub struct ExampleProject {
80    /// Tool environment entries (1..*)
81    #[serde(rename = "environment", default)]
82    pub environments: Vec<ExampleEnvironment>,
83}
84
85#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
86/// Represents an [example project environment](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_example_project_env) entry
87///
88/// Identifies the project file and optional subfolder for a specific development tool
89/// (e.g. `uv`, `iar`, `csolution`).
90pub struct ExampleEnvironment {
91    /// Development tool identifier (e.g. `uv`, `iar`, `csolution`)
92    pub name: String,
93
94    /// Project file path with extension, relative to the example folder
95    pub load: String,
96
97    /// Subdirectory containing tool-specific files, relative to the example folder
98    pub folder: Option<String>,
99}
100
101#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
102/// Represents the [example attributes](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_example_attributes) element
103///
104/// Classification metadata used for example discovery and filtering.
105pub struct ExampleAttributes {
106    /// Free-form category labels (0..*)
107    #[serde(rename = "category", default)]
108    pub categories: Vec<String>,
109
110    /// Component dependencies or tags (0..*)
111    #[serde(rename = "component", default)]
112    pub components: Vec<ExampleComponent>,
113
114    /// Search keywords (0..*)
115    #[serde(rename = "keyword", default)]
116    pub keywords: Vec<String>,
117}
118
119#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
120/// Represents a [component attribute](https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_examples_pg.html#element_example_attribute_component) entry
121///
122/// Tags the example with a component class, group, and optional version for filtering.
123pub struct ExampleComponent {
124    /// Component class
125    #[serde(rename = "Cclass")]
126    pub class: String,
127
128    /// Component group
129    #[serde(rename = "Cgroup")]
130    pub group: Option<String>,
131
132    /// Component sub-group
133    #[serde(rename = "Csub")]
134    pub sub: Option<String>,
135
136    /// Component version
137    #[serde(rename = "Cversion")]
138    pub version: Option<String>,
139
140    /// Component vendor
141    #[serde(rename = "Cvendor")]
142    pub vendor: Option<String>,
143
144    /// Component bundle name
145    #[serde(rename = "Cbundle")]
146    pub bundle: Option<String>,
147
148    /// Component variant name
149    #[serde(rename = "Cvariant")]
150    pub variant: Option<String>,
151
152    /// Component API version
153    #[serde(rename = "Capiversion")]
154    pub api_version: Option<String>,
155
156    /// Number of simultaneous instances allowed; default is 1
157    pub instances: Option<u32>,
158}
159
160#[cfg(test)]
161mod tests {
162    use crate::examples::{
163        ExampleBoard, ExampleComponent, ExampleEnvironment, ExampleProject, Examples,
164    };
165
166    #[test]
167    fn parse_examples() {
168        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
169<examples>
170    <example name="Blinky" folder="examples/Blinky" doc="examples/Blinky/README.md"
171             archive="Blinky.zip" version="1.2.0" public="true">
172        <description>Blinky LED example for Cortex-M4</description>
173        <board vendor="STMicroelectronics" name="NUCLEO-F401RE" revision="B"
174               Dvendor="STMicroelectronics:13" Dname="STM32F401RETx"/>
175        <project>
176            <environment name="uv" load="Blinky.uvprojx"/>
177            <environment name="csolution" load="Blinky.csolution.yml" folder="csolution"/>
178        </project>
179        <attributes>
180            <category>Getting Started</category>
181            <component Cclass="CMSIS" Cgroup="RTOS2" Cversion="2.0.0" Cvendor="ARM"
182                       Cbundle="ARM" Cvariant="Keil" Capiversion="1.0.0" instances="2"/>
183            <keyword>LED</keyword>
184            <keyword>Blinky</keyword>
185        </attributes>
186    </example>
187    <example name="Hello" folder="examples/Hello" doc="examples/Hello/README.md">
188        <description>Hello World via UART</description>
189        <project>
190            <environment name="uv" load="Hello.uvprojx"/>
191        </project>
192    </example>
193</examples>"#;
194
195        let examples: Examples = serde_roxmltree::from_str(xml_str).unwrap();
196        assert_eq!(examples.examples.len(), 2);
197
198        let e0 = &examples.examples[0];
199        assert_eq!(e0.name, "Blinky");
200        assert_eq!(e0.folder, "examples/Blinky");
201        assert_eq!(e0.doc, "examples/Blinky/README.md");
202        assert_eq!(e0.archive, Some("Blinky.zip".to_string()));
203        assert_eq!(e0.version, Some("1.2.0".to_string()));
204        assert_eq!(e0.public, Some(true));
205        assert_eq!(e0.description, "Blinky LED example for Cortex-M4");
206        assert_eq!(
207            e0.boards,
208            vec![ExampleBoard {
209                vendor: "STMicroelectronics".to_string(),
210                name: "NUCLEO-F401RE".to_string(),
211                revision: Some("B".to_string()),
212                device_vendor: Some("STMicroelectronics:13".to_string()),
213                device_name: Some("STM32F401RETx".to_string()),
214            }]
215        );
216        assert_eq!(
217            e0.project,
218            ExampleProject {
219                environments: vec![
220                    ExampleEnvironment {
221                        name: "uv".to_string(),
222                        load: "Blinky.uvprojx".to_string(),
223                        folder: None,
224                    },
225                    ExampleEnvironment {
226                        name: "csolution".to_string(),
227                        load: "Blinky.csolution.yml".to_string(),
228                        folder: Some("csolution".to_string()),
229                    },
230                ],
231            }
232        );
233        let attrs = e0.attributes.as_ref().unwrap();
234        assert_eq!(attrs.categories, vec!["Getting Started".to_string()]);
235        assert_eq!(
236            attrs.components,
237            vec![ExampleComponent {
238                class: "CMSIS".to_string(),
239                group: Some("RTOS2".to_string()),
240                sub: None,
241                version: Some("2.0.0".to_string()),
242                vendor: Some("ARM".to_string()),
243                bundle: Some("ARM".to_string()),
244                variant: Some("Keil".to_string()),
245                api_version: Some("1.0.0".to_string()),
246                instances: Some(2),
247            }]
248        );
249        assert_eq!(
250            attrs.keywords,
251            vec!["LED".to_string(), "Blinky".to_string()]
252        );
253
254        let e1 = &examples.examples[1];
255        assert_eq!(e1.name, "Hello");
256        assert_eq!(e1.archive, None);
257        assert_eq!(e1.version, None);
258        assert_eq!(e1.boards, vec![]);
259        assert_eq!(e1.attributes, None);
260    }
261
262    #[test]
263    fn parse_example_minimal() {
264        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
265<examples>
266    <example name="Minimal" folder="examples/Minimal" doc="examples/Minimal/README.md">
267        <description>Minimal example</description>
268        <project>
269            <environment name="uv" load="Minimal.uvprojx"/>
270        </project>
271    </example>
272</examples>"#;
273
274        let examples: Examples = serde_roxmltree::from_str(xml_str).unwrap();
275        assert_eq!(examples.examples.len(), 1);
276
277        let e = &examples.examples[0];
278        assert_eq!(e.name, "Minimal");
279        assert_eq!(e.folder, "examples/Minimal");
280        assert_eq!(e.doc, "examples/Minimal/README.md");
281        assert_eq!(e.archive, None);
282        assert_eq!(e.version, None);
283        assert_eq!(e.public, None);
284        assert_eq!(e.description, "Minimal example");
285        assert_eq!(e.boards, vec![]);
286        assert_eq!(e.project.environments.len(), 1);
287        assert_eq!(e.project.environments[0].name, "uv");
288        assert_eq!(e.project.environments[0].load, "Minimal.uvprojx");
289        assert_eq!(e.project.environments[0].folder, None);
290        assert_eq!(e.attributes, None);
291    }
292
293    #[test]
294    fn parse_example_attributes() {
295        let xml_str = r#"<?xml version="1.0" encoding="UTF-8"?>
296<examples>
297    <example name="Tagged" folder="examples/Tagged" doc="examples/Tagged/README.md">
298        <description>Example with rich attributes</description>
299        <project>
300            <environment name="csolution" load="Tagged.csolution.yml"/>
301        </project>
302        <attributes>
303            <category>Middleware</category>
304            <category>Networking</category>
305            <component Cclass="Network" Cgroup="Core" Csub="IPv4" Cversion="7.15.0" Cvendor="Keil"/>
306            <component Cclass="CMSIS"/>
307            <keyword>TCP/IP</keyword>
308        </attributes>
309    </example>
310</examples>"#;
311
312        let examples: Examples = serde_roxmltree::from_str(xml_str).unwrap();
313        let e = &examples.examples[0];
314        let attrs = e.attributes.as_ref().unwrap();
315
316        assert_eq!(
317            attrs.categories,
318            vec!["Middleware".to_string(), "Networking".to_string()]
319        );
320        assert_eq!(
321            attrs.components,
322            vec![
323                ExampleComponent {
324                    class: "Network".to_string(),
325                    group: Some("Core".to_string()),
326                    sub: Some("IPv4".to_string()),
327                    version: Some("7.15.0".to_string()),
328                    vendor: Some("Keil".to_string()),
329                    bundle: None,
330                    variant: None,
331                    api_version: None,
332                    instances: None,
333                },
334                ExampleComponent {
335                    class: "CMSIS".to_string(),
336                    group: None,
337                    sub: None,
338                    version: None,
339                    vendor: None,
340                    bundle: None,
341                    variant: None,
342                    api_version: None,
343                    instances: None,
344                },
345            ]
346        );
347        assert_eq!(attrs.keywords, vec!["TCP/IP".to_string()]);
348    }
349}