From f8149b43d463ec3248626fa2cba2ed6f8579bc47 Mon Sep 17 00:00:00 2001 From: John Turner Date: Sat, 22 Nov 2025 02:11:52 +0000 Subject: rearrange modules --- fuzz/atom/parser/gencorpus.rs | 2 +- fuzz/atom/vercmp/gencorpus.rs | 2 +- src/atom/mod.rs | 2 +- src/ebuild/mod.rs | 84 ----------- src/ebuild/parsers.rs | 205 --------------------------- src/ebuild/repo/mod.rs | 316 ----------------------------------------- src/lib.rs | 2 +- src/repo/ebuild/mod.rs | 83 +++++++++++ src/repo/ebuild/parsers.rs | 205 +++++++++++++++++++++++++++ src/repo/mod.rs | 318 ++++++++++++++++++++++++++++++++++++++++++ src/useflag/mod.rs | 2 +- 11 files changed, 611 insertions(+), 610 deletions(-) delete mode 100644 src/ebuild/mod.rs delete mode 100644 src/ebuild/parsers.rs delete mode 100644 src/ebuild/repo/mod.rs create mode 100644 src/repo/ebuild/mod.rs create mode 100644 src/repo/ebuild/parsers.rs create mode 100644 src/repo/mod.rs diff --git a/fuzz/atom/parser/gencorpus.rs b/fuzz/atom/parser/gencorpus.rs index 67ba1e3..299835a 100644 --- a/fuzz/atom/parser/gencorpus.rs +++ b/fuzz/atom/parser/gencorpus.rs @@ -8,7 +8,7 @@ use std::{ use gentoo_utils::{ atom::Atom, - ebuild::{Depend, repo::Repo}, + repo::{Repo, ebuild::Depend}, }; fn main() -> Result<(), Box> { diff --git a/fuzz/atom/vercmp/gencorpus.rs b/fuzz/atom/vercmp/gencorpus.rs index 6d5eeef..6f96f4f 100644 --- a/fuzz/atom/vercmp/gencorpus.rs +++ b/fuzz/atom/vercmp/gencorpus.rs @@ -6,7 +6,7 @@ use std::{ path::PathBuf, }; -use gentoo_utils::ebuild::repo::Repo; +use gentoo_utils::repo::Repo; fn main() -> Result<(), Box> { let corpus_dir = PathBuf::from( diff --git a/src/atom/mod.rs b/src/atom/mod.rs index 0b31223..73f6268 100644 --- a/src/atom/mod.rs +++ b/src/atom/mod.rs @@ -10,7 +10,7 @@ use get::Get; use itertools::Itertools; -pub mod parsers; +mod parsers; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Blocker { diff --git a/src/ebuild/mod.rs b/src/ebuild/mod.rs deleted file mode 100644 index 50d9f3f..0000000 --- a/src/ebuild/mod.rs +++ /dev/null @@ -1,84 +0,0 @@ -use get::Get; -use std::path::PathBuf; - -use crate::{ - atom::{Atom, Name, Slot, Version}, - useflag::{IUseFlag, UseFlag}, -}; - -pub mod parsers; -pub mod repo; - -#[derive(Clone, Debug)] -pub enum Conditional { - Negative(UseFlag), - Positive(UseFlag), -} - -#[derive(Clone, Debug)] -pub enum Depend { - Element(T), - AllOf(Vec), - AnyOf(Vec), - OneOf(Vec), - ConditionalGroup(Conditional, Vec), -} - -#[derive(Debug, Clone)] -pub enum UriPrefix { - Mirror, - Fetch, -} - -#[derive(Debug, Clone, Get)] -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)] -pub struct License(#[get(method = "get", kind = "deref")] String); - -#[derive(Debug, Clone, Get)] -pub struct Eapi(#[get(method = "get", kind = "deref")] String); - -#[derive(Debug, Clone, Get)] -pub struct Eclass(#[get(method = "get", kind = "deref")] String); - -#[derive(Debug, Clone, Get)] -pub struct Ebuild { - name: Name, - 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>, - #[get(kind = "deref")] - rdepend: Vec>, - #[get(kind = "deref")] - idepend: Vec>, -} diff --git a/src/ebuild/parsers.rs b/src/ebuild/parsers.rs deleted file mode 100644 index aa801e8..0000000 --- a/src/ebuild/parsers.rs +++ /dev/null @@ -1,205 +0,0 @@ -use std::path::PathBuf; - -use mon::{ - Parser, ParserIter, ascii_alpha1, ascii_alphanumeric, ascii_whitespace1, r#if, one_of, tag, -}; - -use crate::{ - Parseable, - 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 = ascii_alpha1::<&str>() - .followed_by(tag("://")) - .map(|output: &str| output.to_string()); - let path = r#if(|c: &char| !c.is_ascii_whitespace()) - .repeated() - .at_least(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 filename = || { - r#if(|c: &char| !c.is_ascii_whitespace()) - .repeated() - .at_least(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, - }); - - uri.or(filename().map(|path: PathBuf| SrcUri::Filename(path))) - } -} - -impl<'a> Parseable<'a, &'a str> for License { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let start = ascii_alphanumeric().or(one_of("_".chars())); - let rest = ascii_alphanumeric() - .or(one_of("+_.-".chars())) - .repeated() - .many(); - - start - .and(rest) - .recognize() - .map(|output: &str| License(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for Eapi { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let start = ascii_alphanumeric().or(one_of("_".chars())); - let rest = ascii_alphanumeric() - .or(one_of("+_.-".chars())) - .repeated() - .many(); - - start - .and(rest) - .recognize() - .map(|output: &str| Eapi(output.to_string())) - } -} - -// 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()) - .repeated() - .at_least(1) - .recognize() - .map(|output: &str| Eclass(output.to_string())) - } -} - -impl<'a, T> Parseable<'a, &'a str> for Depend -where - T: Parseable<'a, &'a str>, -{ - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - |it| { - let exprs = || { - Depend::parser() - .separated_by_with_trailing(ascii_whitespace1()) - .at_least(1) - .delimited_by(tag("(").followed_by(ascii_whitespace1()), tag(")")) - }; - - let all_of_group = exprs().map(|exprs| Depend::AllOf(exprs)); - - let any_of_group = exprs() - .preceded_by(tag("||").followed_by(ascii_whitespace1())) - .map(|exprs| Depend::AnyOf(exprs)); - - let one_of_group = exprs() - .preceded_by(tag("^^").followed_by(ascii_whitespace1())) - .map(|exprs| Depend::OneOf(exprs)); - - let conditional_group = Conditional::parser() - .followed_by(ascii_whitespace1()) - .and(exprs()) - .map(|(conditional, exprs)| Depend::ConditionalGroup(conditional, exprs)); - - T::parser() - .map(|e| Depend::Element(e)) - .or(conditional_group) - .or(any_of_group) - .or(all_of_group) - .or(one_of_group) - .parse(it) - } - } -} - -impl<'a> Parseable<'a, &'a str> for Conditional { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - UseFlag::parser() - .preceded_by(tag("!")) - .followed_by(tag("?")) - .map(Conditional::Negative) - .or(UseFlag::parser() - .followed_by(tag("?")) - .map(Conditional::Positive)) - } -} - -#[cfg(test)] -mod test { - - use mon::{ParserIter, input::InputIter}; - - use crate::{atom::Atom, ebuild::Depend}; - - use super::*; - - #[test] - fn test_src_uri() { - let tests = [ - "https://example.com/foo/bar.tar.gz", - "https://example.com/foo/bar.tar.gz -> bar.tar.gz", - ]; - - for test in tests { - SrcUri::parser() - .check_finished(InputIter::new(test)) - .unwrap(); - } - } - - #[test] - fn test_expr() { - let it = InputIter::new("flag? ( || ( foo/bar foo/bar ) )"); - - Depend::::parser() - .separated_by(ascii_whitespace1()) - .many() - .check_finished(it) - .unwrap(); - } -} diff --git a/src/ebuild/repo/mod.rs b/src/ebuild/repo/mod.rs deleted file mode 100644 index ae2113c..0000000 --- a/src/ebuild/repo/mod.rs +++ /dev/null @@ -1,316 +0,0 @@ -use std::{ - fs, io, - path::{Path, PathBuf}, -}; - -use get::Get; - -use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter, tag}; - -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(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), -} - -#[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(PathBuf, fs::ReadDir); - -#[derive(Debug)] -pub struct Ebuilds(PathBuf, fs::ReadDir); - -impl Repo { - pub fn new>(path: P) -> Self { - Self { - path: path.as_ref().to_path_buf(), - } - } - - 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))?, - )) - } -} - -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 { - match self.1.next()? { - Ok(entry) => match read_category(entry.path()) { - Ok(category) => Some(Ok(category)), - Err(e) => Some(Err(e)), - }, - Err(e) => Some(Err(Error::ReadDir(self.0.clone(), e))), - } - } -} - -impl Iterator for Ebuilds { - type Item = Result; - - fn next(&mut self) -> Option { - match self.1.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()).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())) -} diff --git a/src/lib.rs b/src/lib.rs index f41a1a8..beeae1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,5 +16,5 @@ pub trait Parseable<'a, I: Input + 'a> { } pub mod atom; -pub mod ebuild; +pub mod repo; pub mod useflag; diff --git a/src/repo/ebuild/mod.rs b/src/repo/ebuild/mod.rs new file mode 100644 index 0000000..3f52db9 --- /dev/null +++ b/src/repo/ebuild/mod.rs @@ -0,0 +1,83 @@ +use get::Get; +use std::path::PathBuf; + +use crate::{ + atom::{Atom, Name, Slot, Version}, + useflag::{IUseFlag, UseFlag}, +}; + +mod parsers; + +#[derive(Clone, Debug)] +pub enum Conditional { + Negative(UseFlag), + Positive(UseFlag), +} + +#[derive(Clone, Debug)] +pub enum Depend { + Element(T), + AllOf(Vec), + AnyOf(Vec), + OneOf(Vec), + ConditionalGroup(Conditional, Vec), +} + +#[derive(Debug, Clone)] +pub enum UriPrefix { + Mirror, + Fetch, +} + +#[derive(Debug, Clone, Get)] +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)] +pub struct License(#[get(method = "get", kind = "deref")] String); + +#[derive(Debug, Clone, Get)] +pub struct Eapi(#[get(method = "get", kind = "deref")] String); + +#[derive(Debug, Clone, Get)] +pub struct Eclass(#[get(method = "get", kind = "deref")] String); + +#[derive(Debug, Clone, Get)] +pub struct Ebuild { + pub(super) name: Name, + pub(super) version: Version, + pub(super) slot: Option, + pub(super) homepage: Option, + #[get(kind = "deref")] + pub(super) src_uri: Vec>, + pub(super) eapi: Option, + #[get(kind = "deref")] + pub(super) inherit: Vec, + #[get(kind = "deref")] + pub(super) iuse: Vec, + #[get(kind = "deref")] + pub(super) license: Vec>, + pub(super) description: Option, + #[get(kind = "deref")] + pub(super) depend: Vec>, + #[get(kind = "deref")] + pub(super) bdepend: Vec>, + #[get(kind = "deref")] + pub(super) rdepend: Vec>, + #[get(kind = "deref")] + pub(super) idepend: Vec>, +} diff --git a/src/repo/ebuild/parsers.rs b/src/repo/ebuild/parsers.rs new file mode 100644 index 0000000..c80cdf2 --- /dev/null +++ b/src/repo/ebuild/parsers.rs @@ -0,0 +1,205 @@ +use std::path::PathBuf; + +use mon::{ + Parser, ParserIter, ascii_alpha1, ascii_alphanumeric, ascii_whitespace1, r#if, one_of, tag, +}; + +use crate::{ + Parseable, + repo::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 = ascii_alpha1::<&str>() + .followed_by(tag("://")) + .map(|output: &str| output.to_string()); + let path = r#if(|c: &char| !c.is_ascii_whitespace()) + .repeated() + .at_least(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 filename = || { + r#if(|c: &char| !c.is_ascii_whitespace()) + .repeated() + .at_least(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, + }); + + uri.or(filename().map(|path: PathBuf| SrcUri::Filename(path))) + } +} + +impl<'a> Parseable<'a, &'a str> for License { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_alphanumeric().or(one_of("_".chars())); + let rest = ascii_alphanumeric() + .or(one_of("+_.-".chars())) + .repeated() + .many(); + + start + .and(rest) + .recognize() + .map(|output: &str| License(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Eapi { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_alphanumeric().or(one_of("_".chars())); + let rest = ascii_alphanumeric() + .or(one_of("+_.-".chars())) + .repeated() + .many(); + + start + .and(rest) + .recognize() + .map(|output: &str| Eapi(output.to_string())) + } +} + +// 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()) + .repeated() + .at_least(1) + .recognize() + .map(|output: &str| Eclass(output.to_string())) + } +} + +impl<'a, T> Parseable<'a, &'a str> for Depend +where + T: Parseable<'a, &'a str>, +{ + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + |it| { + let exprs = || { + Depend::parser() + .separated_by_with_trailing(ascii_whitespace1()) + .at_least(1) + .delimited_by(tag("(").followed_by(ascii_whitespace1()), tag(")")) + }; + + let all_of_group = exprs().map(|exprs| Depend::AllOf(exprs)); + + let any_of_group = exprs() + .preceded_by(tag("||").followed_by(ascii_whitespace1())) + .map(|exprs| Depend::AnyOf(exprs)); + + let one_of_group = exprs() + .preceded_by(tag("^^").followed_by(ascii_whitespace1())) + .map(|exprs| Depend::OneOf(exprs)); + + let conditional_group = Conditional::parser() + .followed_by(ascii_whitespace1()) + .and(exprs()) + .map(|(conditional, exprs)| Depend::ConditionalGroup(conditional, exprs)); + + T::parser() + .map(|e| Depend::Element(e)) + .or(conditional_group) + .or(any_of_group) + .or(all_of_group) + .or(one_of_group) + .parse(it) + } + } +} + +impl<'a> Parseable<'a, &'a str> for Conditional { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + UseFlag::parser() + .preceded_by(tag("!")) + .followed_by(tag("?")) + .map(Conditional::Negative) + .or(UseFlag::parser() + .followed_by(tag("?")) + .map(Conditional::Positive)) + } +} + +#[cfg(test)] +mod test { + + use mon::{ParserIter, input::InputIter}; + + use crate::{atom::Atom, repo::ebuild::Depend}; + + use super::*; + + #[test] + fn test_src_uri() { + let tests = [ + "https://example.com/foo/bar.tar.gz", + "https://example.com/foo/bar.tar.gz -> bar.tar.gz", + ]; + + for test in tests { + SrcUri::parser() + .check_finished(InputIter::new(test)) + .unwrap(); + } + } + + #[test] + fn test_expr() { + let it = InputIter::new("flag? ( || ( foo/bar foo/bar ) )"); + + Depend::::parser() + .separated_by(ascii_whitespace1()) + .many() + .check_finished(it) + .unwrap(); + } +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs new file mode 100644 index 0000000..46dd895 --- /dev/null +++ b/src/repo/mod.rs @@ -0,0 +1,318 @@ +use std::{ + fs, io, + 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}, + useflag::IUseFlag, +}; + +pub mod ebuild; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[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), +} + +#[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(PathBuf, fs::ReadDir); + +#[derive(Debug)] +pub struct Ebuilds(PathBuf, fs::ReadDir); + +impl Repo { + pub fn new>(path: P) -> Self { + Self { + path: path.as_ref().to_path_buf(), + } + } + + 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))?, + )) + } +} + +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 { + match self.1.next()? { + Ok(entry) => match read_category(entry.path()) { + Ok(category) => Some(Ok(category)), + Err(e) => Some(Err(e)), + }, + Err(e) => Some(Err(Error::ReadDir(self.0.clone(), e))), + } + } +} + +impl Iterator for Ebuilds { + type Item = Result; + + fn next(&mut self) -> Option { + match self.1.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()).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())) +} diff --git a/src/useflag/mod.rs b/src/useflag/mod.rs index 1f814eb..c5a6424 100644 --- a/src/useflag/mod.rs +++ b/src/useflag/mod.rs @@ -2,7 +2,7 @@ use core::fmt; use get::Get; -pub mod parsers; +mod parsers; #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct UseFlag(#[get(method = "name", kind = "deref")] String); -- cgit v1.2.3