cmsis-pack 0.7.2

Rust crate for managing CMSIS Packs
Documentation
use roxmltree::Node;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};
use std::fs::OpenOptions;
use std::io::Read;
use std::path::Path;

use crate::utils::prelude::*;
use anyhow::{format_err, Error};

mod component;
mod condition;
mod device;
pub use component::{ComponentBuilders, FileRef};
pub use condition::{Condition, Conditions};
pub use device::{AccessPort, Algorithm, Core, Device, Devices, Memories, Processor};

pub struct Release {
    pub version: String,
    pub text: String,
}

impl FromElem for Release {
    fn from_elem(e: &Node) -> Result<Self, Error> {
        assert_root_name(e, "release")?;
        Ok(Self {
            version: attr_map(e, "version")?,
            text: e.text().unwrap().to_string(),
        })
    }
}

#[derive(Default)]
pub struct Releases(Vec<Release>);

impl Releases {
    pub fn latest_release(&self) -> &Release {
        &self.0[0]
    }
}

impl FromElem for Releases {
    fn from_elem(e: &Node) -> Result<Self, Error> {
        assert_root_name(e, "releases")?;
        let to_ret: Vec<_> = e
            .children()
            .filter(|e| e.is_element())
            .flat_map(|c| Release::from_elem(&c).ok_warn())
            .collect();
        if to_ret.is_empty() {
            Err(format_err!("There must be at least one release!"))
        } else {
            Ok(Releases(to_ret))
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DumpDevice<'a> {
    name: &'a str,
    memories: Cow<'a, Memories>,
    algorithms: Cow<'a, Vec<Algorithm>>,
    processors: Cow<'a, Vec<Processor>>,
    from_pack: FromPack<'a>,
    vendor: Option<&'a str>,
    family: &'a str,
    sub_family: Option<&'a str>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
struct FromPack<'a> {
    vendor: &'a str,
    pack: &'a str,
    version: &'a str,
    url: &'a str,
}

impl<'a> FromPack<'a> {
    fn new(vendor: &'a str, pack: &'a str, version: &'a str, url: &'a str) -> Self {
        Self {
            vendor,
            pack,
            version,
            url,
        }
    }
}

impl<'a> DumpDevice<'a> {
    fn from_device(dev: &'a Device, from_pack: FromPack<'a>) -> Self {
        Self {
            name: &dev.name,
            memories: Cow::Borrowed(&dev.memories),
            algorithms: Cow::Borrowed(&dev.algorithms),
            processors: Cow::Borrowed(&dev.processors),
            from_pack,
            vendor: dev.vendor.as_deref(),
            family: &dev.family,
            sub_family: dev.sub_family.as_deref(),
        }
    }
}

pub struct Package {
    pub name: String,
    pub description: String,
    pub vendor: String,
    pub url: String,
    pub license: Option<String>,
    components: ComponentBuilders,
    pub releases: Releases,
    pub conditions: Conditions,
    pub devices: Devices,
    pub boards: Vec<Board>,
}

impl FromElem for Package {
    fn from_elem(e: &Node) -> Result<Self, Error> {
        assert_root_name(e, "package")?;
        let name: String = child_text(e, "name")?;
        let description: String = child_text(e, "description")?;
        let vendor: String = child_text(e, "vendor")?;
        let url: String = child_text(e, "url")?;
        log::debug!("Working on {}::{}", vendor, name,);
        let mut components = ComponentBuilders::default();
        let mut releases = Releases::default();
        let mut conditions = Conditions::default();
        let mut devices = Devices::default();
        let mut boards: Vec<Board> = Vec::new();
        for child in e.children() {
            match child.tag_name().name() {
                "components" => {
                    components = ComponentBuilders::from_elem(&child)
                        .ok_warn()
                        .unwrap_or_default();
                }
                "releases" => {
                    releases = Releases::from_elem(&child).ok_warn().unwrap_or_default();
                }
                "conditions" => {
                    conditions = Conditions::from_elem(&child).ok_warn().unwrap_or_default();
                }
                "devices" => {
                    devices = Devices::from_elem(&child).ok_warn().unwrap_or_default();
                }
                "boards" => {
                    boards = Board::vec_from_children(child.children());
                }
                _ => {}
            }
        }
        Ok(Self {
            name,
            description,
            vendor,
            url,
            components,
            license: child_text(e, "license").ok(),
            releases,
            conditions,
            devices,
            boards,
        })
    }
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Board {
    name: String,
    mounted_devices: Vec<String>,
}

impl FromElem for Board {
    fn from_elem(e: &Node) -> Result<Self, Error> {
        Ok(Self {
            name: attr_map(e, "name")?,
            mounted_devices: e
                .children()
                .flat_map(|c| match c.tag_name().name() {
                    "mountedDevice" => attr_map(&c, "Dname").ok(),
                    _ => None,
                })
                .collect(),
        })
    }
}

#[derive(Debug, Serialize)]
pub struct Component {
    pub vendor: String,
    pub class: String,
    pub group: String,
    pub sub_group: Option<String>,
    pub variant: Option<String>,
    pub version: String,
    pub api_version: Option<String>,
    pub condition: Option<String>,
    pub max_instances: Option<u8>,
    pub is_default: bool,
    pub deprecated: bool,
    pub description: String,
    pub rte_addition: String,
    pub files: Vec<FileRef>,
}

type Components = Vec<Component>;

impl Package {
    pub fn make_components(&self) -> Components {
        self.components
            .0
            .clone()
            .into_iter()
            .map(|comp| Component {
                vendor: comp.vendor.unwrap_or_else(|| self.vendor.clone()),
                class: comp.class.unwrap(),
                group: comp.group.unwrap(),
                sub_group: comp.sub_group,
                variant: comp.variant,
                version: comp
                    .version
                    .unwrap_or_else(|| self.releases.latest_release().version.clone()),
                api_version: comp.api_version,
                condition: comp.condition,
                max_instances: comp.max_instances,
                is_default: comp.is_default,
                deprecated: comp.deprecated,
                description: comp.description,
                rte_addition: comp.rte_addition,
                files: comp.files,
            })
            .collect()
    }

    pub fn make_condition_lookup(&self) -> HashMap<&str, &Condition> {
        let mut map = HashMap::with_capacity(self.conditions.0.len());
        for cond in self.conditions.0.iter() {
            if let Some(dup) = map.insert(cond.id.as_str(), cond) {
                log::warn!("Duplicate Condition found {}", dup.id);
            }
        }
        map
    }

    pub fn make_dump_devices(&self) -> Vec<(&str, DumpDevice<'_>)> {
        let from_pack = FromPack::new(
            &self.vendor,
            &self.name,
            &self.releases.latest_release().version,
            &self.url,
        );
        self.devices
            .0
            .iter()
            .map(|(name, d)| (name.as_str(), DumpDevice::from_device(d, from_pack.clone())))
            .collect()
    }
}
pub fn dump_devices<'a, P: AsRef<Path>, I: IntoIterator<Item = &'a Package>>(
    pdscs: I,
    device_dest: Option<P>,
    board_dest: Option<P>,
) -> Result<(), Error> {
    let pdscs: Vec<&Package> = pdscs.into_iter().collect();
    let devices = pdscs
        .iter()
        .flat_map(|pdsc| pdsc.make_dump_devices().into_iter())
        .collect::<HashMap<_, _>>();
    match device_dest {
        Some(to_file) => {
            if !devices.is_empty() {
                let mut file_contents = Vec::new();
                let mut old_devices: HashMap<&str, DumpDevice> = HashMap::new();
                if let Ok(mut fd) = OpenOptions::new().read(true).open(to_file.as_ref()) {
                    fd.read_to_end(&mut file_contents)?;
                    old_devices = serde_json::from_slice(&file_contents).unwrap_or_default();
                }
                let mut all_devices = BTreeMap::new();
                all_devices.extend(old_devices.iter());
                all_devices.extend(devices.iter());
                let mut options = OpenOptions::new();
                options.write(true);
                options.create(true);
                options.truncate(true);
                if let Ok(fd) = options.open(to_file.as_ref()) {
                    serde_json::to_writer_pretty(fd, &all_devices).unwrap();
                } else {
                    println!("Could not open file {:?}", to_file.as_ref());
                }
            }
        }
        None => println!("{}", &serde_json::to_string_pretty(&devices).unwrap()),
    }
    let boards = pdscs
        .iter()
        .flat_map(|pdsc| pdsc.boards.iter())
        .map(|b| (&b.name, b))
        .collect::<HashMap<_, _>>();
    match board_dest {
        Some(to_file) => {
            let mut file_contents = Vec::new();
            let mut old_boards: HashMap<String, Board> = HashMap::new();
            if let Ok(mut fd) = OpenOptions::new().read(true).open(to_file.as_ref()) {
                fd.read_to_end(&mut file_contents)?;
                old_boards = serde_json::from_slice(&file_contents).unwrap_or_default();
            }
            let mut all_boards = BTreeMap::new();
            all_boards.extend(old_boards.iter());
            all_boards.extend(boards.iter());
            let mut options = OpenOptions::new();
            options.write(true);
            options.create(true);
            options.truncate(true);
            if let Ok(fd) = options.open(to_file.as_ref()) {
                serde_json::to_writer_pretty(fd, &all_boards).unwrap();
            } else {
                println!("Could not open file {:?}", to_file.as_ref());
            }
        }
        None => println!("{}", &serde_json::to_string_pretty(&devices).unwrap()),
    }
    Ok(())
}

pub fn dumps_components<'a, I>(pdscs: I) -> Result<String, Error>
where
    I: IntoIterator<Item = &'a Package>,
{
    let components = pdscs
        .into_iter()
        .flat_map(|pdsc| pdsc.make_components().into_iter())
        .collect::<Vec<_>>();
    Ok(serde_json::to_string_pretty(&components)?)
}