use std::error::Error;
use std::path::{Path,PathBuf};
use std::fs::{read_to_string};
use std::fs;
use std::ffi::OsStr;
use std::fmt::{Display,Formatter};
use crate::recipe::Recipe;
pub struct PortsTree {
path: String,
categories: Vec<String>,
ports: Vec<Recipe>,
}
impl Display for PortsTree {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for port in self.ports.iter() {
write!(f, "{}\n", port)?;
}
Ok(())
}
}
impl PortsTree {
pub fn new(path: String) -> PortsTree {
PortsTree {
path: path,
categories: Vec::new(),
ports: Vec::new(),
}
}
pub fn base_path(&mut self) -> Result<PathBuf, Box<dyn Error>> {
let ports_base = Path::new(&self.path);
if !ports_base.is_dir() {
return Err(From::from(format!("Ports tree path unavailable")));
}
Ok(ports_base.to_path_buf())
}
pub fn version(&mut self) -> Result<u8, Box<dyn Error>> {
let base = self.base_path()?;
let repoformat_fh = base.join("FormatVersions");
if !repoformat_fh.is_file() {
return Err(From::from(format!("Ports tree RepositoryFormat unavailable")));
}
for line in read_to_string(repoformat_fh)?.lines() {
if line.starts_with("RecipeFormatVersion=") {
let v: Vec<&str> = line.split('=').collect();
return Ok(v[1].parse::<u8>()?)
}
}
Err(From::from(format!("Ports tree RepositoryFormat invalid")))
}
pub fn rescan(&mut self, primary_arch: String, secondary_arch: Option<String>) -> Result<usize, Box<dyn Error>> {
let base = self.base_path()?;
let version = self.version()?;
assert!(version == 1, "Unknown ports tree version");
self.categories.clear();
self.ports.clear();
let _primary_architecture = primary_arch.clone();
let secondary_architecture = secondary_arch.unwrap_or("".to_string());
for entry in fs::read_dir(base)? {
let entry = entry?;
let category_path = entry.path();
if !category_path.is_dir() {
continue
};
let category_name = category_path.file_name().and_then(OsStr::to_str);
if category_name.is_none() || category_name.unwrap().starts_with(".") {
continue
};
let category_name = category_name.unwrap();
if ["packages", "repository", "stub"].contains(&category_name) {
continue
};
self.categories.push(category_name.to_string());
for port_entry in fs::read_dir(&category_path)? {
let port_entry = port_entry?;
let port_path = port_entry.path();
if !port_path.is_dir() {
continue
}
for recipe_entry in fs::read_dir(port_path)? {
let recipe_entry = recipe_entry?;
let recipe_path = recipe_entry.path();
let extension = recipe_path.extension();
if !recipe_path.is_file() || extension.is_none() || extension.unwrap() != "recipe" {
continue
}
println!("Scanning {}", &recipe_path.display());
let mut recipe = Recipe::new_from_file(&recipe_path, category_name.to_string())?;
let port_version = &recipe.version.clone();
recipe.set_vars("secondaryArchSuffix".to_string(), secondary_architecture.clone());
recipe.set_vars("portVersion".to_string(), port_version.to_string());
self.ports.push(recipe);
}
}
}
Ok(self.ports.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_portstree_new() {
let _portstree = PortsTree::new("samples/ports".to_string());
}
#[test]
fn test_portstree_version() {
let mut portstree = PortsTree::new("sample/ports".to_string());
let version = portstree.version();
assert!(version.is_ok(), "Ports tree version compute failure!");
assert!(version.unwrap() == 1, "Ports tree version mismatch!");
}
#[test]
fn test_portstree_scan() {
let mut portstree = PortsTree::new("sample/ports".to_string());
assert!(portstree.rescan("x86_64".to_string(), Some("x86_gcc2".to_string())).is_ok(),
"Ports tree scan failed!");
}
}