cargo_prosa/
builder.rs

1//! Tool to build project
2//!
3//! Builder contain the strucure of the ProSA.toml file useful to build a ProSA.
4
5use std::{
6    fmt, fs,
7    io::{self, Write},
8    path::Path,
9};
10
11use serde::{Deserialize, Serialize};
12use toml_edit::{Item, Table, Value};
13
14use crate::cargo::{CargoMetadata, ComponentVersion};
15
16/// Descriptor of ProSA main configuration
17///
18/// <svg width="40" height="40">
19#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/main.svg"))]
20/// </svg>
21#[derive(Debug, PartialEq, Deserialize, Serialize)]
22pub struct MainDesc {
23    /// Name of the main task to use for ProSA (`prosa::core::main::MainProc` by default)
24    pub main: String,
25    /// Name of the TVF use for ProSA (`prosa_utils::msg::simple_string_tvf::SimpleStringTvf` by default)
26    pub tvf: String,
27}
28
29impl Default for MainDesc {
30    fn default() -> Self {
31        MainDesc {
32            main: String::from("prosa::core::main::MainProc"),
33            tvf: String::from("prosa_utils::msg::simple_string_tvf::SimpleStringTvf"),
34        }
35    }
36}
37
38/// Descriptor of ProSA processor configuration
39///
40/// <svg width="40" height="40">
41#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/proc.svg"))]
42/// </svg>
43#[derive(Debug, PartialEq, Deserialize, Serialize)]
44pub struct ProcDesc {
45    /// Optional description name (processor name by default)
46    pub name: Option<String>,
47    /// Name of the exposed processor
48    pub proc_name: String,
49    /// Processor to use
50    pub proc: String,
51    /// Adaptor to use
52    pub adaptor: String,
53}
54
55impl ProcDesc {
56    /// Create a new processor desc object
57    pub fn new(proc_name: String, proc: String, adaptor: String) -> Self {
58        ProcDesc {
59            name: None,
60            proc_name,
61            proc,
62            adaptor,
63        }
64    }
65
66    /// Get the name of the processor
67    pub fn get_name(&self) -> String {
68        if let Some(name) = &self.name {
69            name.clone()
70        } else {
71            self.proc_name.clone()
72        }
73    }
74
75    /// Getter of the (processor, adaptor) version from the processor description
76    pub fn get_versions<'a>(
77        &self,
78        cargo_metadata: &'a CargoMetadata,
79    ) -> (Option<ComponentVersion<'a>>, Option<ComponentVersion<'a>>) {
80        cargo_metadata.get_versions(&self.proc, &self.adaptor)
81    }
82}
83
84impl fmt::Display for ProcDesc {
85    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86        writeln!(
87            f,
88            "ProSA processor {} ({})",
89            self.name.as_ref().unwrap_or(&self.proc),
90            self.proc_name,
91        )?;
92        writeln!(f, "  Processor {}", self.proc)?;
93        writeln!(f, "  Adaptor {}", self.adaptor)
94    }
95}
96
97impl TryFrom<&Item> for ProcDesc {
98    type Error = &'static str;
99
100    fn try_from(item: &Item) -> Result<Self, Self::Error> {
101        if let Item::ArrayOfTables(array_tables) = item {
102            let mut name = None;
103            let mut proc_name = None;
104            let mut proc = None;
105            let mut adaptor = None;
106            for array in array_tables {
107                if let Some(Item::Value(Value::String(item_name))) = array.get("name") {
108                    name = Some(item_name.value().clone());
109                } else if let Some(Item::Value(Value::String(item_name))) = array.get("proc_name") {
110                    proc_name = Some(item_name.value().clone());
111                } else if let Some(Item::Value(Value::String(item_name))) = array.get("proc") {
112                    proc = Some(item_name.value().clone());
113                } else if let Some(Item::Value(Value::String(item_name))) = array.get("adaptor") {
114                    adaptor = Some(item_name.value().clone());
115                }
116            }
117
118            if let Some(proc_name) = proc_name {
119                if let Some(proc) = proc {
120                    if let Some(adaptor) = adaptor {
121                        Ok(ProcDesc {
122                            name,
123                            proc_name,
124                            proc,
125                            adaptor,
126                        })
127                    } else {
128                        Err("No `adaptor` key in toml ProSA description")
129                    }
130                } else {
131                    Err("No `proc` key in toml ProSA description")
132                }
133            } else {
134                Err("No `proc_name` key in toml ProSA description")
135            }
136        } else {
137            Err("The item type is not correct for ProSAProxDesc")
138        }
139    }
140}
141
142impl From<ProcDesc> for Table {
143    fn from(proc_desc: ProcDesc) -> Table {
144        let mut table = toml_edit::Table::new();
145        if let Some(name) = proc_desc.name {
146            table.insert(
147                "name",
148                Item::Value(toml_edit::Value::String(toml_edit::Formatted::new(name))),
149            );
150        }
151        table.insert(
152            "proc_name",
153            Item::Value(toml_edit::Value::String(toml_edit::Formatted::new(
154                proc_desc.proc_name,
155            ))),
156        );
157        table.insert(
158            "proc",
159            Item::Value(toml_edit::Value::String(toml_edit::Formatted::new(
160                proc_desc.proc,
161            ))),
162        );
163        table.insert(
164            "adaptor",
165            Item::Value(toml_edit::Value::String(toml_edit::Formatted::new(
166                proc_desc.adaptor,
167            ))),
168        );
169
170        table
171    }
172}
173
174/// Descriptor of ProSA global configuration
175#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
176pub struct Desc {
177    /// ProSA main task descriptor
178    ///
179    /// <svg width="40" height="40">
180    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/main.svg"))]
181    /// </svg>
182    pub prosa: MainDesc,
183    /// ProSA processors descriptors
184    ///
185    /// <svg width="40" height="40">
186    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/doc_assets/proc.svg"))]
187    /// </svg>
188    pub proc: Option<Vec<ProcDesc>>,
189}
190
191impl Desc {
192    /// Method to create a ProSA toml description file
193    pub fn create<P>(&self, path: P) -> Result<(), io::Error>
194    where
195        P: AsRef<Path>,
196    {
197        fn inner(desc: &Desc, mut file: fs::File) -> Result<(), io::Error> {
198            writeln!(file, "# ProSA definition")?;
199            writeln!(
200                file,
201                "{}",
202                toml::to_string(&desc)
203                    .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
204            )
205        }
206        inner(self, fs::File::create(&path)?)
207    }
208
209    /// Method to read a ProSA toml description file
210    pub fn read<P>(path: P) -> Result<Desc, io::Error>
211    where
212        P: AsRef<Path>,
213    {
214        let prosa_desc_file = fs::read_to_string(path)?;
215        toml::from_str::<Desc>(prosa_desc_file.as_str())
216            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
217    }
218
219    /// Method to add a processor to the list
220    #[cfg(test)]
221    pub fn add_proc(&mut self, proc_desc: ProcDesc) {
222        if let Some(proc) = self.proc.as_mut() {
223            proc.push(proc_desc);
224        } else {
225            self.proc = Some(vec![proc_desc]);
226        }
227    }
228}
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn prosa_desc_toml() {
236        let mut prosa_desc = Desc::default();
237        prosa_desc.add_proc(ProcDesc::new(
238            "proc".into(),
239            "crate::proc".into(),
240            "crate::adaptor".into(),
241        ));
242
243        let prosa_toml = "[prosa]
244main = \"prosa::core::main::MainProc\"
245tvf = \"prosa_utils::msg::simple_string_tvf::SimpleStringTvf\"
246
247[[proc]]
248proc_name = \"proc\"
249proc = \"crate::proc\"
250adaptor = \"crate::adaptor\"
251";
252        assert_eq!(prosa_toml, toml::to_string(&prosa_desc).unwrap());
253
254        // FIXME use environment variable when they will be available for unit tests
255        let toml_path_file = Path::new("/tmp/test_prosa_desc.toml");
256        let mut toml_file = fs::File::create(toml_path_file).unwrap();
257        toml_file.write_all(prosa_toml.as_bytes()).unwrap();
258
259        let prosa_desc_from_file = Desc::read(toml_path_file).unwrap();
260        assert_eq!(prosa_desc, prosa_desc_from_file);
261    }
262}