diff --git a/src/ebuild/mod.rs b/src/ebuild/mod.rs index 2558cf4..50d9f3f 100644 --- a/src/ebuild/mod.rs +++ b/src/ebuild/mod.rs @@ -7,6 +7,7 @@ use crate::{ }; pub mod parsers; +pub mod repo; #[derive(Clone, Debug)] pub enum Conditional { @@ -23,10 +24,28 @@ pub enum Depend { ConditionalGroup(Conditional, Vec), } +#[derive(Debug, Clone)] +pub enum UriPrefix { + Mirror, + Fetch, +} + #[derive(Debug, Clone, Get)] -pub struct SrcUri { - uri: String, - file_name: Option, +pub struct Uri { + #[get(kind = "deref")] + protocol: String, + #[get(kind = "deref")] + path: String, +} + +#[derive(Debug, Clone)] +pub enum SrcUri { + Filename(PathBuf), + Uri { + prefix: Option, + uri: Uri, + filename: Option, + }, } #[derive(Debug, Clone, Get)] @@ -44,14 +63,22 @@ pub struct Ebuild { version: Version, slot: Option, homepage: Option, + #[get(kind = "deref")] src_uri: Vec>, eapi: Option, + #[get(kind = "deref")] inherit: Vec, + #[get(kind = "deref")] iuse: Vec, + #[get(kind = "deref")] license: Vec>, description: Option, + #[get(kind = "deref")] depend: Vec>, + #[get(kind = "deref")] bdepend: Vec>, - rdpened: Vec>, + #[get(kind = "deref")] + rdepend: Vec>, + #[get(kind = "deref")] idepend: Vec>, } diff --git a/src/ebuild/parsers.rs b/src/ebuild/parsers.rs index d079609..ef713d2 100644 --- a/src/ebuild/parsers.rs +++ b/src/ebuild/parsers.rs @@ -4,32 +4,59 @@ use mon::{Parser, alpha1, r#if, tag, whitespace1}; use crate::{ Parseable, - ebuild::{Conditional, Depend, Eapi, License, SrcUri}, + ebuild::{Conditional, Depend, Eapi, Eclass, License, SrcUri, Uri, UriPrefix}, useflag::UseFlag, }; +impl<'a> Parseable<'a, &'a str> for UriPrefix { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + tag("+mirror") + .map(|_| UriPrefix::Mirror) + .or(tag("+fetch").map(|_| UriPrefix::Fetch)) + } +} + +impl<'a> Parseable<'a, &'a str> for Uri { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let protocol = alpha1::<&str>() + .followed_by(tag("://")) + .map(|output: &str| output.to_string()); + let path = r#if(|c: &char| !c.is_ascii_whitespace()) + .list(1..) + .recognize() + .map(|output: &str| output.to_string()); + + protocol + .and(path) + .map(|(protocol, path)| Uri { protocol, path }) + } +} + impl<'a> Parseable<'a, &'a str> for SrcUri { type Parser = impl Parser<&'a str, Output = Self>; fn parser() -> Self::Parser { - let protocol = alpha1::<&str>().followed_by(tag("://")); + let filename = || { + r#if(|c: &char| !c.is_ascii_whitespace()) + .list(1..) + .recognize() + .map(|output: &str| PathBuf::from(output)) + }; + let uri = UriPrefix::parser() + .opt() + .and(Uri::parser()) + .and(filename().preceded_by(tag(" -> ")).opt()) + .map(|((prefix, uri), filename)| SrcUri::Uri { + prefix, + uri, + filename, + }); - let uri = r#if(|c: &char| !c.is_ascii_whitespace()) - .list(1..) - .recognize() - .map(|output: &str| output.to_string()); - - let name = r#if(|c: &char| !c.is_ascii_whitespace()) - .list(1..) - .recognize() - .map(|output: &str| PathBuf::from(output)); - - uri.preceded_by(protocol) - .and( - name.preceded_by(tag("->").delimited_by(whitespace1(), whitespace1())) - .opt(), - ) - .map(|(uri, file_name)| SrcUri { uri, file_name }) + uri.or(filename().map(|path: PathBuf| SrcUri::Filename(path))) } } @@ -61,6 +88,20 @@ impl<'a> Parseable<'a, &'a str> for Eapi { } } +// TODO: +// Cant find information about eclass names in pms so we allow anything except +// for whitespace. +impl<'a> Parseable<'a, &'a str> for Eclass { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + r#if(|c: &char| !c.is_ascii_whitespace()) + .list(1..) + .recognize() + .map(|output: &str| Eclass(output.to_string())) + } +} + impl<'a, T> Parseable<'a, &'a str> for Depend where T: Parseable<'a, &'a str>, diff --git a/src/ebuild/repo/mod.rs b/src/ebuild/repo/mod.rs new file mode 100644 index 0000000..0b1f23c --- /dev/null +++ b/src/ebuild/repo/mod.rs @@ -0,0 +1,303 @@ +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_list(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_list(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_list(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_list(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_list(whitespace1(), 0..) + .parse_finished(InputIter::new(line)) + .map_err(|_| Error::Parser(line.to_string())) +} diff --git a/tests/repo.rs b/tests/repo.rs new file mode 100644 index 0000000..77a29b2 --- /dev/null +++ b/tests/repo.rs @@ -0,0 +1,10 @@ +use gentoo_utils::ebuild::repo::Repo; + +#[test] +fn test_read_repo() { + let repo = Repo::new("/var/db/repos/gentoo"); + + for category in repo.categories().unwrap().map(|cat| cat.unwrap()) { + for _ in category.ebuilds().unwrap().map(|ebuild| ebuild.unwrap()) {} + } +}