summaryrefslogtreecommitdiff
path: root/crates/repo/src
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-12-17 06:45:27 +0000
committerJohn Turner <jturner.usa@gmail.com>2025-12-17 06:45:27 +0000
commitc63c3e8c8c73ed7c036df7511ca190cdb96d92e2 (patch)
tree3262ef318f03388d37ac28493e42c1638c227c27 /crates/repo/src
parent0ec856797256b5d9807929e1b32c03756eb43124 (diff)
downloadgentoo-utils-split-into-workspace.tar.gz
Diffstat (limited to 'crates/repo/src')
-rw-r--r--crates/repo/src/ebuild/meson.build1
-rw-r--r--crates/repo/src/ebuild/mod.rs83
-rw-r--r--crates/repo/src/ebuild/parsers.rs207
-rw-r--r--crates/repo/src/lib.rs335
-rw-r--r--crates/repo/src/meson.build3
5 files changed, 629 insertions, 0 deletions
diff --git a/crates/repo/src/ebuild/meson.build b/crates/repo/src/ebuild/meson.build
new file mode 100644
index 0000000..a7331a8
--- /dev/null
+++ b/crates/repo/src/ebuild/meson.build
@@ -0,0 +1 @@
+sources += files('mod.rs', 'parsers.rs')
diff --git a/crates/repo/src/ebuild/mod.rs b/crates/repo/src/ebuild/mod.rs
new file mode 100644
index 0000000..6c547c5
--- /dev/null
+++ b/crates/repo/src/ebuild/mod.rs
@@ -0,0 +1,83 @@
+use get::Get;
+
+use std::path::PathBuf;
+
+use atom::{Atom, Name, Slot, Version};
+
+use useflag::{IUseFlag, UseFlag};
+
+mod parsers;
+
+#[derive(Clone, Debug)]
+pub enum Conditional {
+ Negative(UseFlag),
+ Positive(UseFlag),
+}
+
+#[derive(Clone, Debug)]
+pub enum Depend<T> {
+ Element(T),
+ AllOf(Vec<Self>),
+ AnyOf(Vec<Self>),
+ OneOf(Vec<Self>),
+ ConditionalGroup(Conditional, Vec<Self>),
+}
+
+#[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<UriPrefix>,
+ uri: Uri,
+ filename: Option<PathBuf>,
+ },
+}
+
+#[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<Slot>,
+ pub(super) homepage: Option<String>,
+ #[get(kind = "deref")]
+ pub(super) src_uri: Vec<Depend<SrcUri>>,
+ pub(super) eapi: Option<Eapi>,
+ #[get(kind = "deref")]
+ pub(super) inherit: Vec<Eclass>,
+ #[get(kind = "deref")]
+ pub(super) iuse: Vec<IUseFlag>,
+ #[get(kind = "deref")]
+ pub(super) license: Vec<Depend<License>>,
+ pub(super) description: Option<String>,
+ #[get(kind = "deref")]
+ pub(super) depend: Vec<Depend<Atom>>,
+ #[get(kind = "deref")]
+ pub(super) bdepend: Vec<Depend<Atom>>,
+ #[get(kind = "deref")]
+ pub(super) rdepend: Vec<Depend<Atom>>,
+ #[get(kind = "deref")]
+ pub(super) idepend: Vec<Depend<Atom>>,
+}
diff --git a/crates/repo/src/ebuild/parsers.rs b/crates/repo/src/ebuild/parsers.rs
new file mode 100644
index 0000000..6dc3525
--- /dev/null
+++ b/crates/repo/src/ebuild/parsers.rs
@@ -0,0 +1,207 @@
+use std::path::PathBuf;
+
+use crate::ebuild::{Conditional, Depend, Eapi, Eclass, License, SrcUri, Uri, UriPrefix};
+
+use mon::{
+ Parser, ParserIter, ascii_alpha1, ascii_alphanumeric, ascii_whitespace1, r#if, one_of, tag,
+};
+
+use parseable::Parseable;
+
+use 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<T>
+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 super::*;
+
+ use mon::{ParserIter, input::InputIter};
+
+ use atom::Atom;
+
+ use crate::ebuild::Depend;
+
+ #[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::<Atom>::parser()
+ .separated_by(ascii_whitespace1())
+ .many()
+ .check_finished(it)
+ .unwrap();
+ }
+}
diff --git a/crates/repo/src/lib.rs b/crates/repo/src/lib.rs
new file mode 100644
index 0000000..df4412d
--- /dev/null
+++ b/crates/repo/src/lib.rs
@@ -0,0 +1,335 @@
+#![deny(clippy::pedantic, unused_imports)]
+#![allow(
+ dead_code,
+ unstable_name_collisions,
+ clippy::missing_errors_doc,
+ clippy::missing_panics_doc
+)]
+#![feature(impl_trait_in_assoc_type)]
+
+use std::{
+ fs, io,
+ os::unix::ffi::OsStrExt,
+ path::{Path, PathBuf},
+};
+
+use crate::ebuild::{Depend, Eapi, Ebuild, Eclass, License, SrcUri};
+
+use get::Get;
+
+use mon::{Parser, ParserIter, ascii_whitespace1, input::InputIter, tag};
+
+use parseable::Parseable;
+
+use atom::{self, Atom};
+
+use 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<P: AsRef<Path>>(path: P) -> Self {
+ Self {
+ path: path.as_ref().to_path_buf(),
+ }
+ }
+
+ pub fn categories(&self) -> Result<Categories, Error> {
+ 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<Ebuilds, Error> {
+ 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<Category, Error>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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<Ebuild, Error>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ 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)),
+ },
+ Err(e) => break Some(Err(Error::ReadDir(self.0.clone(), e))),
+ }
+ }
+ }
+}
+
+fn read_category(path: PathBuf) -> Result<Category, Error> {
+ 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<Ebuild, Error> {
+ 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<Result<atom::Slot, Error>> {
+ 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<String> {
+ input
+ .lines()
+ .find_map(|line| line.strip_prefix("HOMEPAGE=").map(str::to_string))
+}
+
+fn read_src_uri(input: &str) -> Option<Result<Vec<Depend<SrcUri>>, Error>> {
+ let line = input
+ .lines()
+ .find_map(|line| line.strip_prefix("SRC_URI="))?;
+
+ match Depend::<SrcUri>::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<Result<Eapi, Error>> {
+ 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<Result<Vec<Eclass>, 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<Result<Vec<IUseFlag>, 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<Result<Vec<Depend<License>>, Error>> {
+ let line = input
+ .lines()
+ .find_map(|line| line.strip_suffix("LICENSE="))?;
+
+ match Depend::<License>::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<String> {
+ input
+ .lines()
+ .find_map(|line| line.strip_prefix("DESCRIPTION=").map(str::to_string))
+}
+
+fn read_depend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> {
+ let line = input
+ .lines()
+ .find_map(|line| line.strip_prefix("DEPEND="))?;
+
+ Some(parse_depends(line))
+}
+
+fn read_bdepend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> {
+ let line = input
+ .lines()
+ .find_map(|line| line.strip_prefix("BDEPEND="))?;
+
+ Some(parse_depends(line))
+}
+
+fn read_rdepend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> {
+ let line = input
+ .lines()
+ .find_map(|line| line.strip_prefix("RDEPEND="))?;
+
+ Some(parse_depends(line))
+}
+
+fn read_idepend(input: &str) -> Option<Result<Vec<Depend<Atom>>, Error>> {
+ let line = input
+ .lines()
+ .find_map(|line| line.strip_prefix("IDEPEND="))?;
+
+ Some(parse_depends(line))
+}
+
+fn parse_depends(line: &str) -> Result<Vec<Depend<Atom>>, Error> {
+ Depend::<Atom>::parser()
+ .separated_by(ascii_whitespace1())
+ .many()
+ .parse_finished(InputIter::new(line))
+ .map_err(|_| Error::Parser(line.to_string()))
+}
diff --git a/crates/repo/src/meson.build b/crates/repo/src/meson.build
new file mode 100644
index 0000000..c1be7a7
--- /dev/null
+++ b/crates/repo/src/meson.build
@@ -0,0 +1,3 @@
+sources += files('mod.rs')
+
+subdir('ebuild')