cargo_prosa/
cargo.rs

1//! Tools for Cargo
2//!
3//! Cargo contain the format of cargo-metadata to retrieve all ProSA dependencies infos.
4//!
5//! To declare a Main task from your Cargo.toml project (don't include your crate name):
6//! ```toml
7//! [package.metadata.prosa]
8//! main = ["core::main::MainProc"]
9//! ```
10//!
11//! To declare a TVF format from your Cargo.toml project (don't include your crate name):
12//! ```toml
13//! [package.metadata.prosa]
14//! tvf = ["msg::simple_string_tvf::SimpleStringTvf"]
15//! ```
16//!
17//! To declare a processor from your Cargo.toml project (don't include your crate name):
18//! ```toml
19//! [package.metadata.prosa.myproc]
20//! proc = "MyProc"
21//! settings = "MySettings"
22//! adaptor = ["MyAdaptor1", "MyAdaptor2"]
23//! ```
24//! or only an adaptor for an existing processor
25//! ```toml
26//! [package.metadata.prosa.myproc]
27//! adaptor = ["MyCustomAdaptor"]
28//! ```
29
30use std::{collections::HashMap, fmt, io};
31
32use serde::Deserialize;
33
34use crate::builder::ProcDesc;
35
36/// Structure to define ProSA component (processor/adaptor) version
37#[derive(Debug, PartialEq)]
38pub struct ComponentVersion<'a> {
39    /// Name of the component
40    pub name: String,
41    /// Name of the component's crate
42    pub crate_name: &'a str,
43    /// Version of the component
44    pub version: &'a str,
45}
46
47impl fmt::Display for ComponentVersion<'_> {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        if f.alternate() {
50            writeln!(f, "{}", self.name)?;
51            writeln!(f, "  crate   {}", self.crate_name)?;
52            writeln!(f, "  version {}", self.version)
53        } else {
54            write!(
55                f,
56                "{} = {{ crate = {}, version = {} }}",
57                self.name, self.crate_name, self.version
58            )
59        }
60    }
61}
62
63/// Metadata of a ProSA processor
64#[derive(Debug, Clone, Deserialize)]
65pub struct Metadata {
66    /// Name of the ProSA package (not included in the metadata)
67    #[serde(skip_deserializing)]
68    pub name: Option<String>,
69    /// Description of the ProSA package (not included in the metadata)
70    #[serde(skip_deserializing)]
71    pub description: Option<String>,
72    /// Struct name of the ProSA processor
73    pub proc: Option<String>,
74    /// Struct name of the ProSA settings
75    pub settings: Option<String>,
76    /// Struct names of ProSA adpators
77    pub adaptor: Option<Vec<String>>,
78}
79
80impl Metadata {
81    /// Method to add the crate name, and description to the metadata
82    fn specify(&mut self, crate_name: &str, description: Option<String>) {
83        self.description = description;
84        let crate_prefix = format!("{crate_name}::");
85
86        if let Some(proc) = &mut self.proc {
87            proc.insert_str(0, crate_prefix.as_str());
88        }
89
90        if let Some(settings) = &mut self.settings {
91            settings.insert_str(0, crate_prefix.as_str());
92        }
93
94        if let Some(adaptor) = &mut self.adaptor {
95            for adaptor in adaptor {
96                adaptor.insert_str(0, crate_prefix.as_str());
97            }
98        }
99    }
100
101    /// Method to merge 2 metadata from the same processor
102    pub fn merge(&mut self, prosa_metadata: Metadata) {
103        if self.proc.is_none() {
104            self.proc = prosa_metadata.proc;
105        }
106
107        if self.settings.is_none() {
108            self.settings = prosa_metadata.settings;
109        }
110
111        if let Some(adaptor_list) = &mut self.adaptor {
112            if let Some(prosa_adaptor_list) = prosa_metadata.adaptor {
113                adaptor_list.extend(prosa_adaptor_list);
114            }
115        } else {
116            self.adaptor = prosa_metadata.adaptor;
117        }
118    }
119
120    /// Method to know if it's the processor from its name
121    pub fn match_proc(&self, name: &str, crate_name: Option<&str>) -> Option<&String> {
122        if let Some(proc) = &self.proc {
123            let proc_name = proc.replace('-', "_");
124            if let Some(crate_name) = crate_name {
125                let proc_name = format!("{crate_name}::{proc_name}");
126                if proc_name.contains(name) {
127                    return Some(proc);
128                }
129            } else if proc_name.contains(name) {
130                return Some(proc);
131            }
132        }
133
134        None
135    }
136
137    /// Method to find an adaptor from its name
138    pub fn find_adaptor(&self, name: &str, crate_name: Option<&str>) -> Option<&String> {
139        if let Some(adaptors) = &self.adaptor {
140            for adaptor in adaptors {
141                let adaptor_name = adaptor.replace('-', "_");
142                if let Some(crate_name) = crate_name {
143                    let adaptor_name = format!("{crate_name}::{adaptor_name}");
144                    if adaptor_name.contains(name) {
145                        return Some(adaptor);
146                    }
147                } else if adaptor_name.contains(name) {
148                    return Some(adaptor);
149                }
150            }
151        }
152
153        None
154    }
155
156    /// Get a ProSA processor description from the metadata
157    pub fn get_proc_desc(
158        &self,
159        adaptor_name: Option<&str>,
160        crate_name: Option<&str>,
161    ) -> Result<ProcDesc, String> {
162        let name = if let Some(name) = &self.name {
163            Some(name.as_str())
164        } else {
165            self.proc
166                .as_ref()
167                .and_then(|p| p.rsplit_once(':').map(|(_, proc_name)| proc_name))
168        }
169        .ok_or(String::from("Missing ProSA `name` metadata"))?;
170
171        let adaptor = if let Some(adaptor) =
172            adaptor_name.and_then(|name| self.find_adaptor(name, crate_name))
173        {
174            Some(adaptor)
175        } else if let Some(adaptors) = &self.adaptor {
176            adaptors.first()
177        } else {
178            None
179        }
180        .ok_or(format!("Can't find a ProSA `adaptor` for {name}"))?;
181
182        Ok(ProcDesc {
183            name: None,
184            proc_name: name.into(),
185            proc: self
186                .proc
187                .clone()
188                .map(|p| p.replace('-', "_"))
189                .ok_or(format!("Missing ProSA `proc` metadata for {name}"))?,
190            adaptor: adaptor.replace('-', "_"),
191        })
192    }
193}
194
195impl fmt::Display for Metadata {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        if let Some(proc) = &self.proc {
198            writeln!(f, "    Processor {proc}")?;
199        }
200
201        if let Some(settings) = &self.settings {
202            writeln!(f, "    Settings {settings}")?;
203        }
204
205        if let Some(adaptor) = &self.adaptor {
206            writeln!(f, "    Adaptor:")?;
207            for adaptor in adaptor {
208                writeln!(f, "     - {adaptor}")?;
209            }
210        }
211
212        Ok(())
213    }
214}
215
216/// Package metadata that describe a crate
217#[derive(Debug, Deserialize)]
218pub struct PackageMetadata {
219    /// Name of the package
220    pub name: String,
221    /// Version of the package
222    pub version: String,
223    /// Package license
224    pub license: Option<String>,
225    /// Description of the package
226    pub description: Option<String>,
227    /// URL of the package documentation
228    pub documentation: Option<String>,
229    /// Authors of the package
230    pub authors: Vec<String>,
231
232    /// Metadata of the package
233    metadata: Option<HashMap<String, serde_json::Value>>,
234    /// target of the package
235    targets: Option<Vec<HashMap<String, serde_json::Value>>>,
236}
237
238impl PackageMetadata {
239    /// Know if a metadata key is present
240    pub fn contain_metadata(&self, name: &str) -> bool {
241        if let Some(metadata) = &self.metadata {
242            metadata.contains_key(name)
243        } else {
244            false
245        }
246    }
247
248    /// Getter of the targets for a specific `kind` (bin, custom-build)
249    pub fn get_targets(&self, kind: &str) -> Option<Vec<String>> {
250        if let Some(targets) = &self.targets {
251            let kind_tera_value = tera::Value::String(kind.to_string());
252            let mut binary_targets = Vec::new();
253            for target in targets {
254                if let Some(kinds) = target.get("kind").and_then(|k| k.as_array())
255                    && kinds.contains(&kind_tera_value)
256                    && let Some(name) = target.get("name").and_then(|k| k.as_str())
257                {
258                    binary_targets.push(name.to_string());
259                }
260            }
261
262            if binary_targets.is_empty() {
263                None
264            } else {
265                Some(binary_targets)
266            }
267        } else {
268            None
269        }
270    }
271
272    /// Know if the package contain ProSA metadata
273    pub fn is_prosa(&self) -> bool {
274        self.contain_metadata("prosa")
275    }
276
277    /// Getter of the ProSA Processor or Adaptor if present
278    pub fn get_prosa_proc_metadata(&self) -> Option<HashMap<&str, Metadata>> {
279        if let Some(metadata) = self
280            .metadata
281            .as_ref()
282            .and_then(|m| m.get("prosa").and_then(|w| w.as_object()))
283        {
284            let mut proc_metadata = HashMap::new();
285            for (name, data) in metadata {
286                if name != "main"
287                    && name != "tvf"
288                    && let Ok(prosa_metadata) = serde_json::from_value::<Metadata>(data.clone())
289                {
290                    proc_metadata.insert(name.as_str(), prosa_metadata);
291                }
292            }
293
294            Some(proc_metadata)
295        } else {
296            None
297        }
298    }
299
300    /// Getter of a ProSA metadata list
301    fn get_prosa_metadata(&self, ty: &str) -> Vec<String> {
302        let mut meta_list: Vec<String> = Vec::new();
303        if let Some(metadata) = self
304            .metadata
305            .as_ref()
306            .and_then(|m| m.get("prosa").and_then(|w| w.as_object()))
307        {
308            for (meta_name, data) in metadata {
309                if meta_name == ty
310                    && let Ok(prosa_metadata) =
311                        serde_json::from_value::<Vec<String>>(data.clone()).map(|v| v.into_iter())
312                {
313                    meta_list.append(
314                        &mut prosa_metadata
315                            .map(|w| format!("{}::{}", self.name.replace('-', "_"), w))
316                            .collect::<Vec<String>>(),
317                    );
318                }
319            }
320        }
321
322        meta_list
323    }
324
325    /// Getter of the ProSA main list
326    pub fn get_prosa_main(&self) -> Vec<String> {
327        self.get_prosa_metadata("main")
328    }
329
330    /// Getter of the ProSA TVF list
331    pub fn get_prosa_tvf(&self) -> Vec<String> {
332        self.get_prosa_metadata("tvf")
333    }
334
335    /// Method to get a component version from its name if it exist
336    fn get_version<'a>(&'a self, name: &str, ty: &str) -> Option<ComponentVersion<'a>> {
337        if let Some(metadata) = self
338            .metadata
339            .as_ref()
340            .and_then(|m| m.get("prosa").and_then(|w| w.as_object()))
341        {
342            for (meta_name, data) in metadata {
343                if meta_name != "main" && meta_name != "tvf" && ty != "main" && ty != "tvf" {
344                    if let Ok(prosa_metadata) = serde_json::from_value::<Metadata>(data.clone()) {
345                        if ty == "proc" {
346                            if let Some(proc_name) = prosa_metadata.match_proc(
347                                name.replace('-', "_").as_str(),
348                                Some(self.name.replace('-', "_").as_str()),
349                            ) {
350                                return Some(ComponentVersion {
351                                    name: proc_name.clone(),
352                                    crate_name: &self.name,
353                                    version: &self.version,
354                                });
355                            }
356                        } else if ty == "adaptor"
357                            && let Some(adaptor_name) = prosa_metadata.find_adaptor(
358                                name.replace('-', "_").as_str(),
359                                Some(self.name.replace('-', "_").as_str()),
360                            )
361                        {
362                            return Some(ComponentVersion {
363                                name: adaptor_name.clone(),
364                                crate_name: &self.name,
365                                version: &self.version,
366                            });
367                        }
368                    }
369                } else if meta_name == ty
370                    && let Some(component_name) =
371                        serde_json::from_value::<Vec<String>>(data.clone())
372                            .ok()
373                            .and_then(|m| {
374                                m.into_iter().find(|w| {
375                                    format!("{}::{}", self.name.replace('-', "_"), w).contains(name)
376                                })
377                            })
378                {
379                    return Some(ComponentVersion {
380                        name: component_name,
381                        crate_name: &self.name,
382                        version: &self.version,
383                    });
384                }
385            }
386        }
387
388        None
389    }
390
391    /// Method to get the processor version from its name if it exist
392    pub fn get_main_version<'a>(&'a self, name: &str) -> Option<ComponentVersion<'a>> {
393        self.get_version(name, "main")
394    }
395
396    /// Method to get the processor version from its name if it exist
397    pub fn get_tvf_version<'a>(&'a self, name: &str) -> Option<ComponentVersion<'a>> {
398        self.get_version(name, "tvf")
399    }
400
401    /// Method to get the processor version from its name if it exist
402    pub fn get_proc_version<'a>(&'a self, name: &str) -> Option<ComponentVersion<'a>> {
403        self.get_version(name, "proc")
404    }
405
406    /// Method to get the adaptor version from its name if it exist
407    pub fn get_adaptor_version<'a>(&'a self, name: &str) -> Option<ComponentVersion<'a>> {
408        self.get_version(name, "adaptor")
409    }
410
411    /// Method to add package metadata to a Jinja context
412    pub fn j2_context(&self, ctx: &mut tera::Context) {
413        ctx.insert("name", &self.name);
414        ctx.insert("version", &self.version);
415        if let Some(license) = &self.license {
416            ctx.insert("license", license);
417        }
418        if let Some(desc) = &self.description {
419            ctx.insert("description", desc);
420        }
421        if let Some(doc) = &self.documentation {
422            ctx.insert("documentation", doc);
423        }
424        if !self.authors.is_empty() {
425            ctx.insert("authors", &self.authors);
426        }
427
428        if let Some(metadata) = &self.metadata {
429            ctx.insert("deb_pkg", &metadata.contains_key("deb"));
430            ctx.insert("rpm_pkg", &metadata.contains_key("generate-rpm"));
431        }
432    }
433}
434
435impl fmt::Display for PackageMetadata {
436    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437        if let Some(description) = &self.description {
438            writeln!(
439                f,
440                "Package {}[{}] ({})",
441                self.name, self.version, description
442            )?;
443        } else {
444            writeln!(f, "Package {}[{}]", self.name, self.version)?;
445        }
446
447        if let Some(metadata) = self
448            .metadata
449            .as_ref()
450            .and_then(|m| m.get("prosa").and_then(|w| w.as_object()))
451        {
452            for (name, data) in metadata {
453                writeln!(f, "  - {name}")?;
454                if name != "main" && name != "tvf" {
455                    if let Ok(prosa_metadata) = serde_json::from_value::<Metadata>(data.clone()) {
456                        write!(f, "{prosa_metadata}")?;
457                    }
458                } else if let Ok(prosa_metadata) =
459                    serde_json::from_value::<Vec<String>>(data.clone())
460                {
461                    for prosa_meta in prosa_metadata {
462                        writeln!(f, "    - {prosa_meta}")?;
463                    }
464                }
465            }
466        }
467
468        Ok(())
469    }
470}
471
472/// Structure that contain all cargo metadata
473#[derive(Debug, Deserialize)]
474pub struct CargoMetadata {
475    packages: Vec<PackageMetadata>,
476}
477
478impl CargoMetadata {
479    /// Method to load metadata for the ProSA package
480    pub fn load_metadata() -> Result<CargoMetadata, io::Error> {
481        // Get packges metadata
482        let cargo_metadata = std::process::Command::new("cargo")
483            .args(vec!["metadata", "-q"])
484            .output()?;
485
486        Ok(serde_json::from_slice(cargo_metadata.stdout.as_slice())?)
487    }
488
489    /// Method to load metadata of the current ProSA package without its dependencies
490    pub fn load_package_metadata() -> Result<PackageMetadata, io::Error> {
491        // Get local packges metadata
492        let cargo_metadata = std::process::Command::new("cargo")
493            .args(vec!["metadata", "-q", "--no-deps"])
494            .output()?;
495        if cargo_metadata.status.success() {
496            let mut metadata: CargoMetadata =
497                serde_json::from_slice(cargo_metadata.stdout.as_slice())?;
498            if metadata.packages.len() == 1 {
499                Ok(metadata.packages.pop().unwrap())
500            } else {
501                Err(io::Error::new(
502                    io::ErrorKind::InvalidData,
503                    "Local package metadata is not correct",
504                ))
505            }
506        } else {
507            Err(io::Error::other(
508                std::str::from_utf8(cargo_metadata.stderr.as_slice()).unwrap_or(
509                    format!(
510                        "Can't retrieve package metadata {:?}",
511                        cargo_metadata.status.code()
512                    )
513                    .as_str(),
514                ),
515            ))
516        }
517    }
518
519    /// Method to get all merged ProSA proc metadata
520    pub fn prosa_proc_metadata(&self) -> HashMap<&str, Metadata> {
521        let mut prosa_list: HashMap<&str, Metadata> = HashMap::with_capacity(self.packages.len());
522        for package in &self.packages {
523            if let Some(prosa_metadata) = package.get_prosa_proc_metadata() {
524                for (prosa_proc_name, mut prosa_proc_metadata) in prosa_metadata {
525                    prosa_proc_metadata.specify(&package.name, package.description.clone());
526                    if let Some(prosa_existing_metadata) = prosa_list.get_mut(prosa_proc_name) {
527                        prosa_existing_metadata.merge(prosa_proc_metadata);
528                    } else {
529                        prosa_list.insert(prosa_proc_name, prosa_proc_metadata);
530                    }
531                }
532            }
533        }
534
535        prosa_list
536    }
537
538    /// Method to get all merged ProSA main proc
539    pub fn prosa_main(&self) -> Vec<String> {
540        let mut main = Vec::new();
541        for package in &self.packages {
542            main.append(&mut package.get_prosa_main());
543        }
544
545        main
546    }
547
548    /// Method to get all merged ProSA TVF format
549    pub fn prosa_tvf(&self) -> Vec<String> {
550        let mut tvf = Vec::new();
551        for package in &self.packages {
552            tvf.append(&mut package.get_prosa_tvf());
553        }
554
555        tvf
556    }
557
558    /// Getter of the main version from its name if it exist
559    pub fn get_main_version<'a>(&'a self, main_name: &str) -> Option<ComponentVersion<'a>> {
560        for package in &self.packages {
561            if let Some(main) = package.get_main_version(main_name) {
562                return Some(main);
563            }
564        }
565
566        None
567    }
568
569    /// Getter of the TVF version from its name if it exist
570    pub fn get_tvf_version<'a>(&'a self, main_name: &str) -> Option<ComponentVersion<'a>> {
571        for package in &self.packages {
572            if let Some(main) = package.get_tvf_version(main_name) {
573                return Some(main);
574            }
575        }
576
577        None
578    }
579
580    /// Getter of the (processor, adaptor) version from their name if it exist
581    pub fn get_versions<'a>(
582        &'a self,
583        proc_name: &str,
584        adaptor_name: &str,
585    ) -> (Option<ComponentVersion<'a>>, Option<ComponentVersion<'a>>) {
586        let mut processor_version = None;
587        let mut adaptor_version = None;
588        for package in &self.packages {
589            if processor_version.is_none()
590                && let Some(proc) = package.get_proc_version(proc_name)
591            {
592                processor_version = Some(proc);
593
594                if adaptor_version.is_some() {
595                    break;
596                }
597            }
598
599            if adaptor_version.is_none()
600                && let Some(adaptor) = package.get_adaptor_version(adaptor_name)
601            {
602                adaptor_version = Some(adaptor);
603
604                if processor_version.is_some() {
605                    break;
606                }
607            }
608        }
609
610        (processor_version, adaptor_version)
611    }
612}
613
614impl fmt::Display for CargoMetadata {
615    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
616        for package in &self.packages {
617            if package.is_prosa() {
618                write!(f, "{package}")?;
619            }
620        }
621
622        Ok(())
623    }
624}