use std::{ fs, io, os::unix::ffi::OsStrExt, path::{Path, PathBuf}, }; use get::Get; use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter, tag}; use crate::{ Parseable, atom::{self, Atom}, repo::{ ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri}, profile::Profile, }, useflag::IUseFlag, }; pub mod ebuild; pub mod profile; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("invalid repo: {0}")] Invalid(String), #[error("io error: {0}")] Io(PathBuf, io::Error), #[error("error while reading directory: {0:?}: {1}")] ReadDir(PathBuf, io::Error), #[error("failed to decode path: {0}")] Unicode(PathBuf), #[error("parser error: {0}")] Parser(String), #[error("profile error: {0}")] Profile(profile::Error), } #[derive(Debug, Clone, Get)] pub struct Repo { #[get(kind = "deref")] path: PathBuf, #[get(kind = "deref")] name: String, } #[derive(Debug, Clone, Get)] pub struct Category { name: atom::Category, #[get(kind = "deref")] path: PathBuf, } #[derive(Debug)] pub struct Categories(PathBuf, fs::ReadDir); #[derive(Debug)] pub struct Ebuilds(PathBuf, fs::ReadDir); impl Repo { pub fn new>(path: P) -> Result { let name_path = path.as_ref().join("profiles/repo_name"); let name = match fs::read_to_string(&name_path) { Ok(repo_name) => repo_name, Err(e) if matches!(e.kind(), io::ErrorKind::NotFound) => { return Err(Error::Invalid("missing repo_name".to_string())); } Err(e) => return Err(Error::Io(name_path, e)), }; Ok(Self { path: path.as_ref().to_path_buf(), name, }) } pub fn categories(&self) -> Result { let path = self.path.as_path().join("metadata/md5-cache"); Ok(Categories( path.clone(), fs::read_dir(&path).map_err(|e| Error::Io(path, e))?, )) } pub fn evaluate_profile>(&self, path: P) -> Result { Profile::evaluate(self.path.join("profiles").join(path)).map_err(Error::Profile) } } impl Category { pub fn ebuilds(&self) -> Result { Ok(Ebuilds( self.path.clone(), fs::read_dir(&self.path).map_err(|e| Error::Io(self.path.clone(), e))?, )) } } impl Iterator for Categories { type Item = Result; fn next(&mut self) -> Option { loop { match self.1.next()? { Ok(entry) if entry.path().file_name().unwrap().as_bytes() == b"Manifest.gz" => (), Ok(entry) => match read_category(entry.path()) { Ok(category) => break Some(Ok(category)), Err(e) => break Some(Err(e)), }, Err(e) => break Some(Err(Error::ReadDir(self.0.clone(), e))), } } } } impl Iterator for Ebuilds { type Item = Result; fn next(&mut self) -> Option { loop { match self.1.next()? { Ok(entry) if entry.path().file_name().unwrap().as_bytes() == b"Manifest.gz" => (), Ok(entry) => match read_ebuild(entry.path()) { Ok(ebuild) => break Some(Ok(ebuild)), Err(e) => break Some(Err(e)), }, _ => todo!(), } } } } fn read_category(path: PathBuf) -> Result { let file_name = path .as_path() .file_name() .unwrap() .to_str() .ok_or(Error::Unicode(path.clone()))?; let name = atom::Category::parser() .parse_finished(InputIter::new(file_name)) .map_err(|_| Error::Parser(file_name.to_string()))?; Ok(Category { name, path }) } fn read_ebuild(path: PathBuf) -> Result { let file_name = path .as_path() .file_name() .unwrap() .to_str() .ok_or(Error::Unicode(path.clone()))?; let (name, version) = atom::Name::parser() .and(atom::Version::parser().preceded_by(tag("-"))) .parse_finished(InputIter::new(file_name)) .map_err(|_| Error::Parser(file_name.to_string()))?; let metadata = fs::read_to_string(path.as_path()).map_err(|e| Error::Io(path, e))?; Ok(Ebuild { name, version, slot: match read_slot(&metadata) { Some(Ok(slot)) => Some(slot), Some(Err(e)) => return Err(e), None => None, }, homepage: read_homepage(&metadata), src_uri: match read_src_uri(&metadata) { Some(Ok(src_uri)) => src_uri, Some(Err(e)) => return Err(e), None => Vec::new(), }, eapi: match read_eapi(&metadata) { Some(Ok(eapi)) => Some(eapi), Some(Err(e)) => return Err(e), None => None, }, inherit: match read_inherit(&metadata) { Some(Ok(inherit)) => inherit, Some(Err(e)) => return Err(e), None => Vec::new(), }, iuse: match read_iuse(&metadata) { Some(Ok(iuse)) => iuse, Some(Err(e)) => return Err(e), None => Vec::new(), }, license: match read_license(&metadata) { Some(Ok(license)) => license, Some(Err(e)) => return Err(e), None => Vec::new(), }, description: read_description(&metadata), depend: match read_depend(&metadata) { Some(Ok(depend)) => depend, Some(Err(e)) => return Err(e), None => Vec::new(), }, bdepend: match read_bdepend(&metadata) { Some(Ok(depend)) => depend, Some(Err(e)) => return Err(e), None => Vec::new(), }, rdepend: match read_rdepend(&metadata) { Some(Ok(depend)) => depend, Some(Err(e)) => return Err(e), None => Vec::new(), }, idepend: match read_idepend(&metadata) { Some(Ok(depend)) => depend, Some(Err(e)) => return Err(e), None => Vec::new(), }, }) } fn read_slot(input: &str) -> Option> { let line = input.lines().find_map(|line| line.strip_prefix("SLOT="))?; match atom::Slot::parser().parse_finished(InputIter::new(line)) { Ok(slot) => Some(Ok(slot)), Err(_) => Some(Err(Error::Parser(line.to_string()))), } } fn read_homepage(input: &str) -> Option { input .lines() .find_map(|line| line.strip_prefix("HOMEPAGE=").map(str::to_string)) } fn read_src_uri(input: &str) -> Option>, Error>> { let line = input .lines() .find_map(|line| line.strip_prefix("SRC_URI="))?; match Depend::::parser() .separated_by(ascii_whitespace1()) .many() .parse_finished(InputIter::new(line)) { Ok(slot) => Some(Ok(slot)), Err(_) => Some(Err(Error::Parser(line.to_string()))), } } fn read_eapi(input: &str) -> Option> { let line = input.lines().find_map(|line| line.strip_prefix("EAPI="))?; match Eapi::parser().parse_finished(InputIter::new(line)) { Ok(slot) => Some(Ok(slot)), Err(_) => Some(Err(Error::Parser(line.to_string()))), } } fn read_inherit(input: &str) -> Option, Error>> { let line = input .lines() .find_map(|line| line.strip_prefix("INHERIT="))?; match Eclass::parser() .separated_by(ascii_whitespace1()) .many() .parse_finished(InputIter::new(line)) { Ok(inherit) => Some(Ok(inherit)), Err(_) => Some(Err(Error::Parser(line.to_string()))), } } fn read_iuse(input: &str) -> Option, Error>> { let line = input.lines().find_map(|line| line.strip_prefix("IUSE="))?; match IUseFlag::parser() .separated_by(ascii_whitespace1()) .many() .parse_finished(InputIter::new(line)) { Ok(iuse) => Some(Ok(iuse)), Err(_) => Some(Err(Error::Parser(line.to_string()))), } } fn read_license(input: &str) -> Option>, Error>> { let line = input .lines() .find_map(|line| line.strip_suffix("LICENSE="))?; match Depend::::parser() .separated_by(ascii_whitespace1()) .many() .parse_finished(InputIter::new(line)) { Ok(license) => Some(Ok(license)), Err(_) => Some(Err(Error::Parser(line.to_string()))), } } fn read_description(input: &str) -> Option { input .lines() .find_map(|line| line.strip_prefix("DESCRIPTION=").map(str::to_string)) } fn read_depend(input: &str) -> Option>, Error>> { let line = input .lines() .find_map(|line| line.strip_prefix("DEPEND="))?; Some(parse_depends(line)) } fn read_bdepend(input: &str) -> Option>, Error>> { let line = input .lines() .find_map(|line| line.strip_prefix("BDEPEND="))?; Some(parse_depends(line)) } fn read_rdepend(input: &str) -> Option>, Error>> { let line = input .lines() .find_map(|line| line.strip_prefix("RDEPEND="))?; Some(parse_depends(line)) } fn read_idepend(input: &str) -> Option>, Error>> { let line = input .lines() .find_map(|line| line.strip_prefix("IDEPEND="))?; Some(parse_depends(line)) } fn parse_depends(line: &str) -> Result>, Error> { Depend::::parser() .separated_by(ascii_whitespace1()) .many() .parse_finished(InputIter::new(line)) .map_err(|_| Error::Parser(line.to_string())) }