use std::{ fs, io, path::{Path, PathBuf}, }; use get::Get; use mon::{Parser, input::InputIter, tag, whitespace1}; use crate::{ Parseable, atom::{self, Atom}, ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri}, useflag::IUseFlag, }; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("io error: {0}")] Io(#[from] io::Error), #[error("failed to decode path: {0}")] Unicode(PathBuf), #[error("parser error: {0}")] Parser(String), } #[derive(Debug, Clone, Get)] pub struct Repo { #[get(kind = "deref")] path: PathBuf, } #[derive(Debug, Clone, Get)] pub struct Category { name: atom::Category, #[get(kind = "deref")] path: PathBuf, } #[derive(Debug)] pub struct Categories(fs::ReadDir); #[derive(Debug)] pub struct Ebuilds(fs::ReadDir); impl Repo { pub fn new>(path: P) -> Self { Self { path: path.as_ref().to_path_buf(), } } pub fn categories(&self) -> Result { Ok(Categories(fs::read_dir( self.path.as_path().join("metadata/md5-cache"), )?)) } } impl Category { pub fn ebuilds(&self) -> Result { Ok(Ebuilds(fs::read_dir(self.path.as_path())?)) } } impl Iterator for Categories { type Item = Result; fn next(&mut self) -> Option { match self.0.next()? { Ok(entry) => match read_category(entry.path()) { Ok(category) => Some(Ok(category)), Err(e) => Some(Err(e)), }, Err(e) => Some(Err(Error::Io(e))), } } } impl Iterator for Ebuilds { type Item = Result; fn next(&mut self) -> Option { match self.0.next()? { Ok(entry) => match read_ebuild(entry.path()) { Ok(ebuild) => Some(Ok(ebuild)), Err(e) => 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())?; 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(|s| s.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(whitespace1(), 0..) .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(whitespace1(), 0..) .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(whitespace1(), 0..) .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(whitespace1(), 0..) .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(|s| s.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(whitespace1(), 0..) .parse_finished(InputIter::new(line)) .map_err(|_| Error::Parser(line.to_string())) }