ydev 0.1.1

Yocto development helper tool.
Documentation
#[allow(unused)]
use {
    crate::recipe::Recipe,
    crate::utils::{scan_dir, scan_dir_for_dir, scan_dir_for_file},
    crate::yocto::{DataStore, YoctoVar, YoctoVarName, YoctoVarType},
    anyhow::{Context, Error, Result},
    jlogger::{jdebug, jerror, jinfo, jwarn},
    log::{debug, error, info, warn},
    regex::Regex,
    std::collections::HashMap,
    std::fmt::Display,
    std::fs,
    std::path::{Path, PathBuf},
};

pub struct Layer {
    collection: String,
    priority: u32,
    machine: Vec<String>,
    bbclass: Vec<String>,
    bb: HashMap<String, Vec<Recipe>>,
}

impl Layer {
    pub fn new(layer_dir: &str, gstore: Option<&DataStore>) -> Result<Self> {
        let mut data_store = DataStore::new();

        let mut overrides = String::new();

        if let Some(d) = gstore {
            if let Some(yv) = d.value("OVERRIDES") {
                overrides.push_str(yv.value(None))
            }
        }

        /* LAYERDIR is a special variable for current directory */
        let mut yv = YoctoVar::new(Some("LAYERDIR"))?;
        yv.set_value(YoctoVarType::Value, layer_dir);
        data_store.add(yv);

        if let Some(d) = gstore {
            if d.value("HOSTTOOLS_DIR").is_none() {
                data_store.add(YoctoVar::new(Some("HOSTTOOLS_DIR"))?);
            }
        }

        data_store.from_conf_file(format!("{}/conf/layer.conf", layer_dir).as_str(), gstore)?;

        let collection = data_store
            .value("BBFILE_COLLECTIONS")
            .ok_or(Error::msg(format!(
                "No BBFILE_COLLECTIONS in {}",
                layer_dir
            )))?
            .value(Some(&overrides));

        overrides.push_str(format!(":{}", collection).as_str());

        let priority: u32 = data_store
            .value("BBFILE_PRIORITY")
            .ok_or(Error::msg(format!("No BBFILE_PRIORITY in {}", layer_dir)))?
            .value(Some(&overrides))
            .parse()?;

        let mut machine = Vec::<String>::new();
        let machine_dir_str = format!("{}/conf/machine", layer_dir);
        let machine_dir = Path::new(machine_dir_str.as_str());
        if machine_dir.is_dir() {
            let _ = scan_dir_for_file(machine_dir, &mut |p: PathBuf| -> Result<()> {
                if let Some(l) = p.file_name() {
                    if let Some(f) = l.to_str() {
                        if f.ends_with("conf") {
                            machine.push(f.trim_end_matches(".conf").to_string());
                        } else {
                            jwarn!("neglect {} for machine", p.to_str().unwrap());
                        }
                    }
                }
                Ok(())
            });
        }

        let mut bbclass = Vec::<String>::new();
        let mut bb = HashMap::<String, Vec<Recipe>>::new();
        let mut bbfiles = Vec::new();
        let yv = data_store
            .value("BBFILES")
            .ok_or(Error::msg(format!("No BBFILES found in {}", layer_dir)))?;

        data_store
            .expand_value(yv.value(Some(&overrides)), gstore)?
            .split(" ")
            .filter(|a| (*a).trim() != "\\" && !(*a).trim().is_empty())
            .for_each(|a| {
                let t = a
                    .trim()
                    .trim_end_matches("\\")
                    .trim()
                    .trim_start_matches("${LAYERDIR}/");
                bbfiles.push(t.to_string());
            });

        let mut process = Vec::new();

        let mut res = Vec::new();
        let mut depth = 0_usize;

        for patterns in &bbfiles {
            let re_pattern = patterns.replace("*", ".+");
            let re = Regex::new(&re_pattern).unwrap();
            res.push(re);

            let v = re_pattern.split("/").collect::<Vec<&str>>();
            if v.len() > depth {
                depth = v.len();
            }
        }

        process.push(layer_dir.to_string());
        while !process.is_empty() {
            let mut next_process = Vec::new();
            for entry in &process {
                scan_dir(Path::new(entry), &mut |p| {
                    if p.is_dir() {
                        let pname = p.to_str().unwrap();
                        let depth_now = pname.split("/").collect::<Vec<&str>>().len();
                        if depth_now < depth {
                            next_process.push(pname.to_string());
                        }
                    }

                    if p.is_file() {
                        let f = p.to_str().unwrap();
                        if f.ends_with("bb") || f.ends_with("bbappend") {
                            for re in &res {
                                if re.is_match(f) {
                                    let r = Recipe::new(f).unwrap();
                                    let key = r.name().to_string();
                                    if let Some(v) = bb.get_mut(key.as_str()) {
                                        let mut existed = false;

                                        v.iter().for_each(|a| {
                                            if a.path() == r.path() {
                                                existed = true;
                                            }
                                        });

                                        if !existed {
                                            v.push(r);
                                        }
                                    } else {
                                        let mut v = Vec::new();
                                        v.push(r);
                                        bb.insert(key, v);
                                    }

                                    break;
                                }
                            }
                        }
                    }
                    true
                });
            }
            process = next_process;
        }

        let bbclass_dir_str = format!("{}/classes", layer_dir);
        let bbclass_dir = Path::new(bbclass_dir_str.as_str());
        if bbclass_dir.is_dir() {
            let _ = scan_dir_for_file(bbclass_dir, &mut |p| {
                let filename = p.file_name().unwrap().to_str().unwrap();
                if filename.ends_with(".bbclass") {
                    bbclass.push(p.to_str().unwrap().to_string());
                }
                Ok(())
            });
        }

        Ok(Layer {
            collection: collection.to_string(),
            priority,
            machine,
            bbclass,
            bb,
        })
    }

    pub fn collection(&self) -> &str {
        self.collection.as_str()
    }

    pub fn recipes(&self) -> &HashMap<String, Vec<Recipe>> {
        &self.bb
    }
}

impl Display for Layer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut output = String::new();
        output.push_str(format!("Collection: {}\n", self.collection).as_str());
        output.push_str(format!("Priority: {}\n", self.priority).as_str());
        if !self.machine.is_empty() {
            output.push_str(format!("Machine ({}):\n", self.machine.len()).as_str());
            for m in &self.machine {
                output.push_str(format!("  {}\n", m).as_str());
            }
        }

        if !self.bb.is_empty() {
            output.push_str(format!("BB ({}):\n", self.bb.len()).as_str());
            for k in self.bb.keys() {
                output.push_str(format!("  {}\n", k).as_str());
            }
        }

        if !self.bbclass.is_empty() {
            output.push_str(format!("BBCLASS ({}):\n", self.bbclass.len()).as_str());
            for b in &self.bbclass {
                let p = Path::new(b).file_name().unwrap().to_str().unwrap();
                output.push_str(format!("  {}\n", p).as_str());
            }
        }

        write!(f, "{}", output)
    }
}