From c63c3e8c8c73ed7c036df7511ca190cdb96d92e2 Mon Sep 17 00:00:00 2001 From: John Turner Date: Wed, 17 Dec 2025 06:45:27 +0000 Subject: debugging --- Cargo.lock | 63 +-- Cargo.toml | 19 +- crates/atom/Cargo.toml | 11 + crates/atom/meson.build | 9 + crates/atom/src/lib.rs | 786 +++++++++++++++++++++++++++++++++++++ crates/atom/src/meson.build | 1 + crates/atom/src/parsers.rs | 599 ++++++++++++++++++++++++++++ crates/meson.build | 1 + crates/parseable/Cargo.toml | 7 + crates/parseable/src/lib.rs | 18 + crates/repo/Cargo.toml | 11 + crates/repo/src/ebuild/meson.build | 1 + crates/repo/src/ebuild/mod.rs | 83 ++++ crates/repo/src/ebuild/parsers.rs | 207 ++++++++++ crates/repo/src/lib.rs | 335 ++++++++++++++++ crates/repo/src/meson.build | 3 + crates/useflag/Cargo.toml | 9 + crates/useflag/src/lib.rs | 29 ++ crates/useflag/src/meson.build | 1 + crates/useflag/src/parsers.rs | 39 ++ meson.build | 44 +-- src/atom/meson.build | 1 - src/atom/mod.rs | 772 ------------------------------------ src/atom/parsers.rs | 599 ---------------------------- src/lib.rs | 81 +--- src/meson.build | 4 - src/repo/ebuild/meson.build | 1 - src/repo/ebuild/mod.rs | 83 ---- src/repo/ebuild/parsers.rs | 205 ---------- src/repo/meson.build | 3 - src/repo/mod.rs | 325 --------------- src/useflag/meson.build | 1 - src/useflag/mod.rs | 20 - src/useflag/parsers.rs | 40 -- 34 files changed, 2212 insertions(+), 2199 deletions(-) create mode 100644 crates/atom/Cargo.toml create mode 100644 crates/atom/meson.build create mode 100644 crates/atom/src/lib.rs create mode 100644 crates/atom/src/meson.build create mode 100644 crates/atom/src/parsers.rs create mode 100644 crates/meson.build create mode 100644 crates/parseable/Cargo.toml create mode 100644 crates/parseable/src/lib.rs create mode 100644 crates/repo/Cargo.toml create mode 100644 crates/repo/src/ebuild/meson.build create mode 100644 crates/repo/src/ebuild/mod.rs create mode 100644 crates/repo/src/ebuild/parsers.rs create mode 100644 crates/repo/src/lib.rs create mode 100644 crates/repo/src/meson.build create mode 100644 crates/useflag/Cargo.toml create mode 100644 crates/useflag/src/lib.rs create mode 100644 crates/useflag/src/meson.build create mode 100644 crates/useflag/src/parsers.rs delete mode 100644 src/atom/meson.build delete mode 100644 src/atom/mod.rs delete mode 100644 src/atom/parsers.rs delete mode 100644 src/repo/ebuild/meson.build delete mode 100644 src/repo/ebuild/mod.rs delete mode 100644 src/repo/ebuild/parsers.rs delete mode 100644 src/repo/meson.build delete mode 100644 src/repo/mod.rs delete mode 100644 src/useflag/meson.build delete mode 100644 src/useflag/mod.rs delete mode 100644 src/useflag/parsers.rs diff --git a/Cargo.lock b/Cargo.lock index dd0d9c6..7a7deb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "atom" +version = "0.1.0" +dependencies = [ + "get", + "itertools", + "mon", + "parseable", + "useflag", +] + [[package]] name = "either" version = "1.15.0" @@ -12,10 +23,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" name = "gentoo-utils" version = "0.1.0" dependencies = [ - "get", - "itertools", - "mon", - "thiserror", + "atom", + "repo", + "useflag", ] [[package]] @@ -42,6 +52,13 @@ name = "mon" version = "0.1.0" source = "git+https://jturnerusa.dev/cgit/mon/?rev=67861a4df8a5abdd70651d47cf265b20c41d2acc#67861a4df8a5abdd70651d47cf265b20c41d2acc" +[[package]] +name = "parseable" +version = "0.1.0" +dependencies = [ + "mon", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -60,6 +77,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "repo" +version = "0.1.0" +dependencies = [ + "atom", + "get", + "mon", + "parseable", + "useflag", +] + [[package]] name = "syn" version = "2.0.107" @@ -72,27 +100,16 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "2.0.17" +name = "unicode-ident" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl", -] +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +name = "useflag" +version = "0.1.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "get", + "mon", + "parseable", ] - -[[package]] -name = "unicode-ident" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" diff --git a/Cargo.toml b/Cargo.toml index 3f99b99..c6c8296 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,21 @@ +[workspace] +members = [ + "crates/parseable", + "crates/atom", + "crates/useflag", + "crates/repo" +] + +[workspace.dependencies] +mon = { git = "https://jturnerusa.dev/cgit/mon/", rev = "67861a4df8a5abdd70651d47cf265b20c41d2acc" } +get = { git = "https://jturnerusa.dev/cgit/get/", rev = "cd5f75b65777a855ab010c3137304ac05f2e56b8" } + [package] name = "gentoo-utils" version = "0.1.0" edition = "2024" [dependencies] -mon = { git = "https://jturnerusa.dev/cgit/mon/", rev = "67861a4df8a5abdd70651d47cf265b20c41d2acc" } -get = { git = "https://jturnerusa.dev/cgit/get/", rev = "cd5f75b65777a855ab010c3137304ac05f2e56b8" } -itertools = "0.14.0" -thiserror = "2.0.17" \ No newline at end of file +atom = { path = "crates/atom" } +useflag = { path = "crates/useflag" } +repo = { path = "crates/repo" } \ No newline at end of file diff --git a/crates/atom/Cargo.toml b/crates/atom/Cargo.toml new file mode 100644 index 0000000..092759f --- /dev/null +++ b/crates/atom/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "atom" +version = "0.1.0" +edition = "2024" + +[dependencies] +mon = { workspace = true } +get = { workspace = true } +useflag = { path = "../useflag" } +parseable = { path = "../parseable" } +itertools = "0.14.0" \ No newline at end of file diff --git a/crates/atom/meson.build b/crates/atom/meson.build new file mode 100644 index 0000000..ff4b6e6 --- /dev/null +++ b/crates/atom/meson.build @@ -0,0 +1,9 @@ +subdir('src') + +pkg = cargo.package('atom') +lib = pkg.library() + +meson.override_dependency( + 'atom-' + pkg.api() + '-rs', + declare_dependency(link_with: lib), +) diff --git a/crates/atom/src/lib.rs b/crates/atom/src/lib.rs new file mode 100644 index 0000000..39e32af --- /dev/null +++ b/crates/atom/src/lib.rs @@ -0,0 +1,786 @@ +#![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 core::{ + fmt::{self}, + option::Option, +}; + +use std::cmp::Ordering; + +// TODO: wtf? +#[allow(unused_imports)] +use parseable::Parseable; + +use useflag::UseFlag; + +use get::Get; + +use itertools::Itertools; + +mod parsers; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Blocker { + Weak, + Strong, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum VersionOperator { + Lt, + Gt, + Eq, + LtEq, + GtEq, + Roughly, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct Category(#[get(method = "get", kind = "deref")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct Name(#[get(method = "get", kind = "deref")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct VersionNumber(#[get(method = "get", kind = "deref")] String); + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Get)] +struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum VersionSuffixKind { + Alpha, + Beta, + Pre, + Rc, + P, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Get)] +pub struct VersionSuffix { + kind: VersionSuffixKind, + number: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Get)] +pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec); + +#[derive(Debug, Clone, Get, PartialEq, Eq, Hash)] +pub struct BuildId(#[get(method = "get", kind = "deref")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct Version { + numbers: VersionNumbers, + letter: Option, + suffixes: VersionSuffixes, + rev: Option, + build_id: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Wildcard; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum SlotOperator { + Eq, + Star, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct SlotName(#[get(method = "name", kind = "deref")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Slot { + Wildcard, + Equal, + NameEqual { + primary: SlotName, + sub: Option, + }, + Name { + primary: SlotName, + sub: Option, + }, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum UseDepNegate { + Minus, + Exclamation, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum UseDepSign { + Enabled, + Disabled, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum UseDepCondition { + Eq, + Question, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Repo(String); + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct UseDep { + negate: Option, + flag: UseFlag, + sign: Option, + condition: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct Cp { + category: Category, + name: Name, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct Cpv { + category: Category, + name: Name, + version: Version, + slot: Option, +} + +#[derive(Clone, Debug, Get, PartialEq, Eq, Hash)] +pub struct Atom { + blocker: Option, + category: Category, + name: Name, + version: Option<(VersionOperator, Version, Option)>, + slot: Option, + repo: Option, + #[get(kind = "deref")] + usedeps: Vec, +} + +impl Cpv { + #[must_use] + pub fn into_cp(self) -> Cp { + Cp { + name: self.name, + category: self.category, + } + } +} + +impl Atom { + #[must_use] + pub fn version_operator(&self) -> Option { + self.version.clone().map(|(oper, _, _)| oper) + } + + #[must_use] + pub fn into_cp(self) -> Cp { + Cp { + category: self.category, + name: self.name, + } + } + + #[must_use] + pub fn into_cpv(self) -> Option { + match self.version { + Some((_, version, _)) => Some(Cpv { + category: self.category, + name: self.name, + version, + slot: self.slot, + }), + None => None, + } + } +} + +impl VersionNumber { + #[must_use] + pub fn cmp_as_ints(&self, other: &Self) -> Ordering { + let a = self.get().trim_start_matches('0'); + let b = other.get().trim_start_matches('0'); + + a.len().cmp(&b.len()).then_with(|| a.cmp(b)) + } + + #[must_use] + pub fn cmp_as_str(&self, other: &Self) -> Ordering { + if self.get().starts_with('0') || other.get().starts_with('0') { + let a = self.get().trim_end_matches('0'); + let b = other.get().trim_end_matches('0'); + + a.cmp(b) + } else { + self.cmp_as_ints(other) + } + } +} + +impl PartialOrd for BuildId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for BuildId { + fn cmp(&self, other: &Self) -> Ordering { + // build-id may not start with a zero so we dont need to strip them + self.get() + .len() + .cmp(&other.get().len()) + .then_with(|| self.get().cmp(other.get())) + } +} + +impl PartialOrd for VersionSuffix { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VersionSuffix { + fn cmp(&self, other: &Self) -> Ordering { + match &self.kind.cmp(&other.kind) { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => match (&self.number, &other.number) { + (Some(a), Some(b)) => a.cmp_as_ints(b), + (Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal, + (None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal, + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, + }, + } + } +} + +impl PartialOrd for VersionSuffixes { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VersionSuffixes { + fn cmp(&self, other: &Self) -> Ordering { + let mut a = self.get().iter(); + let mut b = other.get().iter(); + + loop { + match (a.next(), b.next()) { + (Some(a), Some(b)) => match a.cmp(b) { + Ordering::Less => break Ordering::Less, + Ordering::Greater => break Ordering::Greater, + Ordering::Equal => (), + }, + (Some(a), None) if matches!(a.kind, VersionSuffixKind::P) => { + break Ordering::Greater; + } + (Some(_), None) => break Ordering::Less, + (None, Some(b)) if matches!(b.kind, VersionSuffixKind::P) => break Ordering::Less, + (None, Some(_)) => break Ordering::Greater, + (None, None) => break Ordering::Equal, + } + } + } +} + +impl PartialOrd for VersionNumbers { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VersionNumbers { + fn cmp(&self, other: &Self) -> Ordering { + match self + .get() + .first() + .unwrap() + .cmp_as_ints(other.get().first().unwrap()) + { + Ordering::Less => Ordering::Less, + Ordering::Greater => Ordering::Greater, + Ordering::Equal => { + let mut a = self.get().iter().skip(1); + let mut b = other.get().iter().skip(1); + + loop { + match (a.next(), b.next()) { + (Some(a), Some(b)) => match a.cmp_as_str(b) { + Ordering::Less => break Ordering::Less, + Ordering::Greater => break Ordering::Greater, + Ordering::Equal => (), + }, + + (Some(_), None) => break Ordering::Greater, + (None, Some(_)) => break Ordering::Less, + (None, None) => break Ordering::Equal, + } + } + } + } + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + match self.numbers.cmp(&other.numbers) { + Ordering::Less => return Ordering::Less, + Ordering::Greater => return Ordering::Greater, + Ordering::Equal => (), + } + + match (self.letter, other.letter) { + (Some(a), Some(b)) if a < b => return Ordering::Less, + (Some(a), Some(b)) if a > b => return Ordering::Greater, + (Some(a), Some(b)) if a == b => (), + (Some(_), None) => return Ordering::Greater, + (None, Some(_)) => return Ordering::Less, + (None, None) => (), + _ => unreachable!(), + } + + match self.suffixes.cmp(&other.suffixes) { + Ordering::Less => return Ordering::Less, + Ordering::Greater => return Ordering::Greater, + Ordering::Equal => (), + } + + match (&self.rev, &other.rev) { + (Some(a), Some(b)) => match a.cmp_as_ints(b) { + Ordering::Less => return Ordering::Less, + Ordering::Greater => return Ordering::Greater, + Ordering::Equal => (), + }, + (Some(a), None) if a.get().chars().all(|c| c == '0') => (), + (Some(_), None) => return Ordering::Greater, + (None, Some(b)) if b.get().chars().all(|c| c == '0') => (), + (None, Some(_)) => return Ordering::Less, + (None, None) => (), + } + + match (&self.build_id, &other.build_id) { + (Some(a), Some(b)) => a.cmp(b), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, + } + } +} + +impl PartialOrd for Cpv { + fn partial_cmp(&self, other: &Self) -> Option { + if self.category == other.category && self.name == other.name { + Some(self.version.cmp(&other.version)) + } else { + None + } + } +} + +impl fmt::Display for Blocker { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Weak => write!(f, "!"), + Self::Strong => write!(f, "!!"), + } + } +} + +impl fmt::Display for VersionOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Lt => write!(f, "<"), + Self::Gt => write!(f, ">"), + Self::Eq => write!(f, "="), + Self::LtEq => write!(f, "<="), + Self::GtEq => write!(f, ">="), + Self::Roughly => write!(f, "~"), + } + } +} + +impl fmt::Display for Category { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Display for Name { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Display for VersionNumber { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Display for BuildId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.get()) + } +} + +impl fmt::Display for VersionSuffixKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Alpha => write!(f, "alpha"), + Self::Beta => write!(f, "beta"), + Self::Pre => write!(f, "pre"), + Self::Rc => write!(f, "rc"), + Self::P => write!(f, "p"), + } + } +} + +impl fmt::Display for VersionSuffix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.kind)?; + + if let Some(number) = self.number.as_ref() { + write!(f, "{number}")?; + } + + Ok(()) + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let numbers = self + .numbers + .get() + .iter() + .map(VersionNumber::get) + .intersperse(".") + .collect::(); + + let suffixes = self + .suffixes + .get() + .iter() + .map(VersionSuffix::to_string) + .intersperse("_".to_string()) + .collect::(); + + write!(f, "{numbers}")?; + + if let Some(letter) = self.letter { + write!(f, "{letter}")?; + } + + if !suffixes.is_empty() { + write!(f, "_{suffixes}")?; + } + + if let Some(rev) = self.rev.as_ref() { + write!(f, "-r{rev}")?; + } + + if let Some(build_id) = self.build_id.as_ref() { + write!(f, "-{build_id}")?; + } + + Ok(()) + } +} + +impl fmt::Display for SlotOperator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Eq => write!(f, "="), + Self::Star => write!(f, "*"), + } + } +} + +impl fmt::Display for SlotName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl fmt::Display for Slot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Wildcard => write!(f, "*"), + Self::Equal => { + write!(f, "=") + } + Self::NameEqual { primary, sub } => { + write!(f, "{primary}")?; + + if let Some(sub) = sub { + write!(f, "/{sub}")?; + } + + write!(f, "=") + } + Self::Name { primary, sub } => { + write!(f, "{primary}")?; + + if let Some(sub) = sub { + write!(f, "/{sub}")?; + } + + Ok(()) + } + } + } +} + +impl fmt::Display for UseDepNegate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Minus => write!(f, "-"), + Self::Exclamation => write!(f, "!"), + } + } +} + +impl fmt::Display for UseDepSign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Enabled => write!(f, "(+)"), + Self::Disabled => write!(f, "(-)"), + } + } +} + +impl fmt::Display for UseDepCondition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Eq => write!(f, "="), + Self::Question => write!(f, "?"), + } + } +} + +impl fmt::Display for UseDep { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(negate) = self.negate.as_ref() { + write!(f, "{negate}")?; + } + + write!(f, "{}", self.flag)?; + + if let Some(sign) = self.sign.as_ref() { + write!(f, "{sign}")?; + } + + if let Some(condition) = self.condition.as_ref() { + write!(f, "{condition}")?; + } + + Ok(()) + } +} + +impl fmt::Display for Cp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}", &self.category, &self.name) + } +} + +impl fmt::Display for Cpv { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}-{}", &self.category, &self.name, &self.version)?; + + if let Some(slot) = self.slot.as_ref() { + write!(f, ":{slot}")?; + } + + Ok(()) + } +} + +impl fmt::Display for Atom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(blocker) = self.blocker.as_ref() { + write!(f, "{blocker}")?; + } + + if let Some(version_operator) = self.version_operator().as_ref() { + write!(f, "{version_operator}")?; + } + + write!(f, "{}", self.category)?; + write!(f, "/")?; + write!(f, "{}", self.name)?; + + if let Some((_, version, None)) = self.version() { + write!(f, "-{version}")?; + } else if let Some((_, version, Some(_))) = self.version() { + write!(f, "-{version}*")?; + } + + if let Some(slot) = self.slot.as_ref() { + write!(f, ":{slot}")?; + } + + let usedeps = self + .usedeps + .iter() + .map(UseDep::to_string) + .intersperse(",".to_string()) + .collect::(); + + if !usedeps.is_empty() { + write!(f, "[{usedeps}]")?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use mon::{Parser, input::InputIter}; + + use super::*; + + use crate::Parseable; + + macro_rules! assert_cmp_display { + ($a:expr, $b:expr, $ordering:expr) => { + if $a.cmp(&$b) != $ordering { + panic!("{} ~ {} != {:?}", $a, $b, $ordering) + } + }; + } + + macro_rules! assert_partial_cmp_display { + ($a:expr, $b:expr, $ordering:expr) => { + if $a.partial_cmp(&$b) != $ordering { + panic!("{} ~ {} != {:?}", $a, $b, $ordering) + } + }; + } + + #[test] + fn test_version_display() { + let s = "1.0.0_alpha1_beta1-r1"; + let version = Version::parser().parse_finished(InputIter::new(s)).unwrap(); + + assert_eq!(version.to_string().as_str(), s); + } + + #[test] + fn test_display_atom() { + let s = "!!>=foo/bar-1.0.0v_alpha1_beta1-r1:slot/sub=[a,b,c]"; + let atom = Atom::parser().parse_finished(InputIter::new(s)).unwrap(); + + assert_eq!(atom.to_string().as_str(), s); + } + + #[test] + fn test_version_cmp() { + let versions = [ + ("1.0.1", "1.0", Ordering::Greater), + ("1.0.0", "1.0.0_alpha", Ordering::Greater), + ("1.0.0_alpha", "1.0.0_alpha_p", Ordering::Less), + ("1.0.0-r0", "1.0.0", Ordering::Equal), + ("1.0.0-r0000", "1.0.0", Ordering::Equal), + ("1.0.0-r1-1", "1.0.0-r1-2", Ordering::Less), + ]; + + for (a, b, ordering) in versions.iter().map(|(a, b, ordering)| { + ( + Version::parser().parse_finished(InputIter::new(a)).unwrap(), + Version::parser().parse_finished(InputIter::new(b)).unwrap(), + ordering, + ) + }) { + assert_cmp_display!(a, b, *ordering); + } + } + + #[test] + fn test_cpv_eq() { + let cpvs = [ + ("foo/bar-1", "foo/bar-1", Some(Ordering::Equal)), + ("foo/baz-1", "foo/bar-1", None), + ]; + + for (a, b, ordering) in cpvs.iter().copied().map(|(a, b, ordering)| { + ( + Cpv::parser().parse_finished(InputIter::new(a)).unwrap(), + Cpv::parser().parse_finished(InputIter::new(b)).unwrap(), + ordering, + ) + }) { + assert_partial_cmp_display!(a, b, ordering); + } + } + + #[test] + fn test_version_cmp_letter() { + let a = Version::parser() + .parse_finished(InputIter::new("1.0.0")) + .unwrap(); + let b = Version::parser() + .parse_finished(InputIter::new("1.0.0a")) + .unwrap(); + + assert_cmp_display!(a, b, Ordering::Less); + } + + #[test] + fn test_version_cmp_where_b_has_leading_zeros() { + let a = Version::parser() + .parse_finished(InputIter::new("1.2")) + .unwrap(); + let b = Version::parser() + .parse_finished(InputIter::new("1.054")) + .unwrap(); + + assert_cmp_display!(a, b, Ordering::Greater); + } + + #[test] + fn test_version_has_more_zeros() { + let a = Version::parser() + .parse_finished(InputIter::new("1.0.0")) + .unwrap(); + let b = Version::parser() + .parse_finished(InputIter::new("1.0")) + .unwrap(); + + assert_cmp_display!(a, b, Ordering::Greater); + } + + #[test] + fn test_fuzzer_cases() { + let control = Version::parser() + .parse_finished(InputIter::new("1.2.0a_alpha1_beta2-r1-8")) + .unwrap(); + + #[allow(clippy::single_element_loop)] + for (version_str, expected) in [("1.2.0", Ordering::Greater)] { + let version = Version::parser() + .parse_finished(InputIter::new(version_str)) + .unwrap(); + + assert_cmp_display!(control, version, expected); + } + } +} diff --git a/crates/atom/src/meson.build b/crates/atom/src/meson.build new file mode 100644 index 0000000..0293be6 --- /dev/null +++ b/crates/atom/src/meson.build @@ -0,0 +1 @@ +sources += files('lib.rs', 'parsers.rs') diff --git a/crates/atom/src/parsers.rs b/crates/atom/src/parsers.rs new file mode 100644 index 0000000..2f8cb8c --- /dev/null +++ b/crates/atom/src/parsers.rs @@ -0,0 +1,599 @@ +use core::option::Option::None; + +use crate::{ + Atom, Blocker, BuildId, Category, Cp, Cpv, Name, Repo, Slot, SlotName, SlotOperator, + UseDep, UseDepCondition, UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers, + VersionOperator, VersionSuffix, VersionSuffixKind, VersionSuffixes, Wildcard, +}; + +use mon::{ + Parser, ParserIter, ascii_alphanumeric, ascii_numeric, ascii_numeric1, eof, r#if, + input::InputIter, one_of, tag, +}; + +use parseable::Parseable; + +use useflag::UseFlag; + +impl<'a> Parseable<'a, &'a str> for Blocker { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + tag("!!") + .map(|_| Blocker::Strong) + .or(tag("!").map(|_| Blocker::Weak)) + } +} + +impl<'a> Parseable<'a, &'a str> for VersionOperator { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + tag("<=") + .map(|_| VersionOperator::LtEq) + .or(tag(">=").map(|_| VersionOperator::GtEq)) + .or(tag("<").map(|_| VersionOperator::Lt)) + .or(tag(">").map(|_| VersionOperator::Gt)) + .or(tag("=").map(|_| VersionOperator::Eq)) + .or(tag("~").map(|_| VersionOperator::Roughly)) + } +} + +impl<'a> Parseable<'a, &'a str> for VersionNumber { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + ascii_numeric1().map(|output: &str| VersionNumber(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for BuildId { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_numeric().and_not(tag("0")); + let rest = ascii_numeric().repeated().many(); + + start + .and(rest) + .recognize() + .or(tag("0")) + .map(|output: &str| BuildId(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for VersionSuffixKind { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + tag("alpha") + .map(|_| VersionSuffixKind::Alpha) + .or(tag("beta").map(|_| VersionSuffixKind::Beta)) + .or(tag("pre").map(|_| VersionSuffixKind::Pre)) + .or(tag("rc").map(|_| VersionSuffixKind::Rc)) + .or(tag("p").map(|_| VersionSuffixKind::P)) + } +} + +impl<'a> Parseable<'a, &'a str> for VersionSuffix { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + VersionSuffixKind::parser() + .and(VersionNumber::parser().opt()) + .map(|(kind, number)| VersionSuffix { kind, number }) + } +} + +impl<'a> Parseable<'a, &'a str> for VersionNumbers { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + VersionNumber::parser() + .separated_by(tag(".")) + .at_least(1) + .map(VersionNumbers) + } +} + +impl<'a> Parseable<'a, &'a str> for VersionSuffixes { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + VersionSuffix::parser() + .separated_by(tag("_")) + .at_least(1) + .map(VersionSuffixes) + } +} + +impl<'a> Parseable<'a, &'a str> for Version { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let rev = VersionNumber::parser().preceded_by(tag("-r")); + let build_id = BuildId::parser().preceded_by(tag("-")); + + VersionNumbers::parser() + .and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt()) + .and(VersionSuffixes::parser().preceded_by(tag("_")).opt()) + .and(rev.opt()) + .and(build_id.opt()) + .map(|((((numbers, letter), suffixes), rev), build_id)| Version { + numbers, + letter, + suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())), + rev, + build_id, + }) + } +} + +impl<'a> Parseable<'a, &'a str> for Category { + 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| Category(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Name { + 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())) + .or(one_of("-".chars()).and_not( + Version::parser() + .preceded_by(tag("-")) + .followed_by(ascii_alphanumeric().or(one_of("_+-".chars())).not()), + )) + .repeated() + .many(); + + let verify = ascii_alphanumeric() + .or(one_of("_+".chars())) + .or(one_of("-".chars()) + .and_not(Version::parser().preceded_by(tag("-")).followed_by(eof()))) + .repeated() + .many(); + + start() + .and(rest) + .recognize() + .verify_output(move |output: &&str| { + verify.check_finished(InputIter::new(*output)).is_ok() + }) + .map(|output: &str| Name(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for SlotOperator { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + tag("=") + .map(|_| SlotOperator::Eq) + .or(tag("*").map(|_| SlotOperator::Star)) + } +} + +impl<'a> Parseable<'a, &'a str> for SlotName { + 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| SlotName(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for Slot { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let wildcard = tag("*").map(|_| Slot::Wildcard); + let equal = tag("=").map(|_| Slot::Equal); + let name_equal = SlotName::parser() + .and(SlotName::parser().preceded_by(tag("/")).opt()) + .followed_by(tag("=")) + .map(|(primary, sub)| Slot::NameEqual { primary, sub }); + let name = SlotName::parser() + .and(SlotName::parser().preceded_by(tag("/")).opt()) + .map(|(primary, sub)| Self::Name { primary, sub }); + + wildcard.or(equal).or(name_equal).or(name) + } +} + +impl<'a> Parseable<'a, &'a str> for UseDepSign { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + tag("(-)") + .map(|_| UseDepSign::Disabled) + .or(tag("(+)").map(|_| UseDepSign::Enabled)) + } +} + +impl<'a> Parseable<'a, &'a str> for Repo { + 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() + .verify_output(move |output: &&str| { + Name::parser() + .check_finished(InputIter::new(*output)) + .is_ok() + }) + .map(|output: &str| Repo(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for UseDep { + type Parser = impl Parser<&'a str, Output = Self>; + + #[allow(clippy::many_single_char_names)] + fn parser() -> Self::Parser { + let a = UseFlag::parser() + .and(UseDepSign::parser().opt()) + .preceded_by(tag("-")) + .map(|(flag, sign)| UseDep { + negate: Some(UseDepNegate::Minus), + flag, + sign, + condition: None, + }); + + let b = UseFlag::parser() + .and(UseDepSign::parser().opt()) + .preceded_by(tag("!")) + .followed_by(tag("?")) + .map(|(flag, sign)| UseDep { + negate: Some(UseDepNegate::Exclamation), + flag, + sign, + condition: Some(UseDepCondition::Question), + }); + + let c = UseFlag::parser() + .and(UseDepSign::parser().opt()) + .followed_by(tag("?")) + .map(|(flag, sign)| UseDep { + negate: None, + flag, + sign, + condition: Some(UseDepCondition::Question), + }); + + let d = UseFlag::parser() + .and(UseDepSign::parser().opt()) + .preceded_by(tag("!")) + .followed_by(tag("=")) + .map(|(flag, sign)| UseDep { + negate: Some(UseDepNegate::Exclamation), + flag, + sign, + condition: Some(UseDepCondition::Eq), + }); + + let e = UseFlag::parser() + .and(UseDepSign::parser().opt()) + .followed_by(tag("=")) + .map(|(flag, sign)| UseDep { + negate: None, + flag, + sign, + condition: Some(UseDepCondition::Eq), + }); + + let f = UseFlag::parser() + .and(UseDepSign::parser().opt()) + .map(|(flag, sign)| UseDep { + negate: None, + flag, + sign, + condition: None, + }); + + a.or(b).or(c).or(d).or(e).or(f) + } +} + +impl<'a> Parseable<'a, &'a str> for Atom { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let usedeps = || { + UseDep::parser() + .separated_by(tag(",")) + .at_least(1) + .delimited_by(tag("["), tag("]")) + .opt() + }; + + let without_version = Blocker::parser() + .opt() + .and(Category::parser()) + .and(Name::parser().preceded_by(tag("/"))) + .and(Slot::parser().preceded_by(tag(":")).opt()) + .and(Repo::parser().preceded_by(tag("::")).opt()) + .and(usedeps()) + .map( + |(((((blocker, category), name), slot), repo), usedeps)| Atom { + blocker, + category, + name, + version: None, + slot, + repo, + usedeps: usedeps.unwrap_or(Vec::new()), + }, + ); + + let with_version = Blocker::parser() + .opt() + .and(VersionOperator::parser()) + .and(Category::parser()) + .and(Name::parser().preceded_by(tag("/"))) + .and(Version::parser().preceded_by(tag("-"))) + .and(tag("*").map(|_| Wildcard).opt()) + .and(Slot::parser().preceded_by(tag(":")).opt()) + .and(Repo::parser().preceded_by(tag("::")).opt()) + .and(usedeps()) + .verify_output( + |((((((((_, version_operator), _), _), version), star), _), _), _)| { + matches!( + (version_operator, star), + (VersionOperator::Eq, Some(_) | None) | (_, None) + ) && matches!((version.build_id(), star), (Some(_), None) | (None, _)) + }, + ) + .map( + |( + ( + ((((((blocker, version_operator), category), name), version), star), slot), + repo, + ), + usedeps, + )| { + Atom { + blocker, + category, + name, + version: Some((version_operator, version, star)), + slot, + repo, + usedeps: usedeps.unwrap_or(Vec::new()), + } + }, + ); + + with_version.or(without_version) + } +} + +impl<'a> Parseable<'a, &'a str> for Cp { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Category::parser() + .and(Name::parser().preceded_by(tag("/"))) + .map(|(category, name)| Cp { category, name }) + } +} + +impl<'a> Parseable<'a, &'a str> for Cpv { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + Category::parser() + .and(Name::parser().preceded_by(tag("/"))) + .and(Version::parser().preceded_by(tag("-"))) + .and(Slot::parser().preceded_by(tag(":")).opt()) + .map(|(((category, name), version), slot)| Cpv { + category, + name, + version, + slot, + }) + } +} + +#[cfg(test)] +mod test { + + use mon::input::InputIter; + + use super::*; + + #[test] + fn test_version() { + let it = InputIter::new("1.0.0v_alpha1_beta1-r1"); + + Version::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_name() { + let it = InputIter::new("foo-1-bar-1.0.0"); + + match Name::parser().parse(it) { + Ok((_, output)) => { + assert_eq!(output.0.as_str(), "foo-1-bar"); + } + _ => unreachable!(), + } + } + + #[test] + fn test_atom() { + let it = InputIter::new( + "!!>=cat/pkg-1-foo-1.0.0v_alpha1_p20250326-r1:primary/sub=[use,use=,!use=,use?,!use?,-use,use(+),use(-)]", + ); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_cursed_atom() { + let it = InputIter::new( + "!!>=_.+-0-/_-test-T-123_beta1_-4a-6+-_p--1.00.02b_alpha3_pre_p4-r5:slot/_-+6-9=[test(+),test(-)]", + ); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_atom_with_star_in_non_empty_slot() { + let it = InputIter::new("foo/bar:*/subslot"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_invalid_usedep() { + let it = InputIter::new("foo-bar:slot/sub=[!use]"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_empty_slot() { + let it = InputIter::new("=dev-ml/uucp-17*:"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_usedep_with_underscore() { + let it = InputIter::new("foo/bar[use_dep]"); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_version_with_uppercase_letter() { + let it = InputIter::new("=foo/bar-1.0.0V"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_version_with_version_operator_without_version() { + let it = InputIter::new("=foo/bar"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_version_with_version_without_version_operator() { + let it = InputIter::new("foo/bar-1.0.0"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_atom_with_eq_version_operator() { + let it = InputIter::new("=foo/bar-1.0.0"); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_atom_with_star_in_version() { + let it = InputIter::new("=foo/bar-1.2*"); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_atom_with_star_in_version_without_eq_version_operator() { + let it = InputIter::new(">=foo/bar-1.2*"); + + assert!(Atom::parser().check_finished(it).is_err()); + } + + #[test] + fn test_atom_with_trailing_dash_and_letter() { + let it = InputIter::new("dev-db/mysql-connector-c"); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_cpv_with_slot() { + let it = InputIter::new("foo/bar-1.0:slot/sub="); + + Cpv::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_cpv_without_version_but_trailing_almost_version() { + let it = InputIter::new("dev-perl/mod-p-2.3_"); + + assert!(Cpv::parser().parse_finished(it).is_err()); + } + + #[test] + fn test_empty_slot_with_operator() { + let it = InputIter::new("foo/bar:="); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_with_repo() { + let it = InputIter::new("=foo/bar-1.0.0:slot/sub=::gentoo[a,b,c]"); + + Atom::parser().check_finished(it).unwrap(); + } + + #[test] + fn test_against_fuzzer_false_positives() { + let atoms = [ + "media-libs/libsdl2[haptitick(+),sound(+)vd,eio(+)]", + "=kde-frameworks/kcodecs-6.19*86", + "=dev-ml/stdio-0.17*t:=[ocamlopt?]", + ">=dev-libs/libgee-0-8.5:0..8=", + "=kde-frameworks/kcrash-2.16.0:6*", + "0-f/merreka+m::k+", + "iev-a/h:/n=", + "=dev-ml/stdio-0-17*:=[ocamlopt?]", + ]; + + for atom in atoms { + assert!( + Atom::parser().check_finished(InputIter::new(atom)).is_err(), + "{atom}" + ); + } + } +} diff --git a/crates/meson.build b/crates/meson.build new file mode 100644 index 0000000..e64f43d --- /dev/null +++ b/crates/meson.build @@ -0,0 +1 @@ +subdir('atom') diff --git a/crates/parseable/Cargo.toml b/crates/parseable/Cargo.toml new file mode 100644 index 0000000..25b0c77 --- /dev/null +++ b/crates/parseable/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "parseable" +version = "0.1.0" +edition = "2024" + +[dependencies] +mon = { workspace = true } \ No newline at end of file diff --git a/crates/parseable/src/lib.rs b/crates/parseable/src/lib.rs new file mode 100644 index 0000000..38ed418 --- /dev/null +++ b/crates/parseable/src/lib.rs @@ -0,0 +1,18 @@ +#![feature(impl_trait_in_assoc_type)] + +use mon::{Parser, input::{Input, InputIter}}; + +pub trait Parseable<'a, I: Input + 'a> { + type Parser: Parser; + + fn parser() -> Self::Parser; + + fn parse(input: I) -> Result + where + Self: Sized, + { + Self::parser() + .parse_finished(InputIter::new(input)) + .map_err(|e| e.rest()) + } +} diff --git a/crates/repo/Cargo.toml b/crates/repo/Cargo.toml new file mode 100644 index 0000000..8b13ede --- /dev/null +++ b/crates/repo/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "repo" +version = "0.1.0" +edition = "2024" + +[dependencies] +atom = { path = "../atom" } +useflag = { path = "../useflag" } +parseable = { path = "../parseable" } +mon = { workspace = true } +get = { workspace = true } \ No newline at end of file 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 { + 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/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 +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::::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>(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 { + 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)), + }, + Err(e) => break Some(Err(Error::ReadDir(self.0.clone(), e))), + } + } + } +} + +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/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') diff --git a/crates/useflag/Cargo.toml b/crates/useflag/Cargo.toml new file mode 100644 index 0000000..16c8c34 --- /dev/null +++ b/crates/useflag/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "useflag" +version = "0.1.0" +edition = "2024" + +[dependencies] +mon = { workspace = true } +get = { workspace = true } +parseable = { path = "../parseable" } \ No newline at end of file diff --git a/crates/useflag/src/lib.rs b/crates/useflag/src/lib.rs new file mode 100644 index 0000000..75c40eb --- /dev/null +++ b/crates/useflag/src/lib.rs @@ -0,0 +1,29 @@ +#![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 core::fmt; + +use get::Get; + +mod parsers; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct UseFlag(#[get(method = "name", kind = "deref")] String); + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] +pub struct IUseFlag { + default: bool, + flag: UseFlag, +} + +impl fmt::Display for UseFlag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/crates/useflag/src/meson.build b/crates/useflag/src/meson.build new file mode 100644 index 0000000..a7331a8 --- /dev/null +++ b/crates/useflag/src/meson.build @@ -0,0 +1 @@ +sources += files('mod.rs', 'parsers.rs') diff --git a/crates/useflag/src/parsers.rs b/crates/useflag/src/parsers.rs new file mode 100644 index 0000000..3007bde --- /dev/null +++ b/crates/useflag/src/parsers.rs @@ -0,0 +1,39 @@ +use parseable::Parseable; + +use mon::{Parser, ParserIter, ascii_alphanumeric, one_of, tag}; + +use crate::{IUseFlag, UseFlag}; + +impl<'a> Parseable<'a, &'a str> for UseFlag { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + let start = ascii_alphanumeric(); + let rest = ascii_alphanumeric() + .or(one_of("+_@-".chars())) + .repeated() + .many(); + + start + .and(rest) + .recognize() + .map(|output: &str| UseFlag(output.to_string())) + } +} + +impl<'a> Parseable<'a, &'a str> for IUseFlag { + type Parser = impl Parser<&'a str, Output = Self>; + + fn parser() -> Self::Parser { + UseFlag::parser() + .preceded_by(tag("+")) + .map(|flag| IUseFlag { + default: true, + flag, + }) + .or(UseFlag::parser().map(|flag| IUseFlag { + default: false, + flag, + })) + } +} diff --git a/meson.build b/meson.build index 389720b..291eeb7 100644 --- a/meson.build +++ b/meson.build @@ -7,48 +7,12 @@ project( rustfmt = find_program('rustfmt') -rust = import('rust') fs = import('fs') +rust = import('rust') +cargo = rust.workspace() + sources = [] subdir('src') - -mon = dependency('mon-0.1-rs') -get = dependency('get-0.1-rs') -itertools = dependency('itertools-0.14-rs') -thiserror = subproject('thiserror').get_variable('thiserror') - -gentoo_utils = static_library( - 'gentoo_utils', - 'src/lib.rs', - dependencies: [mon, get, itertools], - link_with: [thiserror], -) - -custom_target( - 'rustfmt', - input: sources, - output: 'rustfmt', - command: [rustfmt, '--edition=2024', '--check', '@INPUT@'], - build_always_stale: true, -) - -if get_option('tests').enabled() - rust.test('unittests', gentoo_utils) - subdir('tests') -endif - -if get_option('fuzz').enabled() - subdir('fuzz') -endif - -if get_option('docs').enabled() - rust.doctest( - 'doctests', - gentoo_utils, - dependencies: [mon, get, itertools], - link_with: [thiserror], - args: ['--nocapture'], - ) -endif +subdir('crates') diff --git a/src/atom/meson.build b/src/atom/meson.build deleted file mode 100644 index a7331a8..0000000 --- a/src/atom/meson.build +++ /dev/null @@ -1 +0,0 @@ -sources += files('mod.rs', 'parsers.rs') diff --git a/src/atom/mod.rs b/src/atom/mod.rs deleted file mode 100644 index 24cb555..0000000 --- a/src/atom/mod.rs +++ /dev/null @@ -1,772 +0,0 @@ -use core::{ - fmt::{self}, - option::Option, -}; -use std::cmp::Ordering; - -use crate::useflag::UseFlag; - -use get::Get; - -use itertools::Itertools; - -mod parsers; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Blocker { - Weak, - Strong, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum VersionOperator { - Lt, - Gt, - Eq, - LtEq, - GtEq, - Roughly, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct Category(#[get(method = "get", kind = "deref")] String); - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct Name(#[get(method = "get", kind = "deref")] String); - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct VersionNumber(#[get(method = "get", kind = "deref")] String); - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Get)] -struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec); - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum VersionSuffixKind { - Alpha, - Beta, - Pre, - Rc, - P, -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq, Get)] -pub struct VersionSuffix { - kind: VersionSuffixKind, - number: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Get)] -pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec); - -#[derive(Debug, Clone, Get, PartialEq, Eq, Hash)] -pub struct BuildId(#[get(method = "get", kind = "deref")] String); - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct Version { - numbers: VersionNumbers, - letter: Option, - suffixes: VersionSuffixes, - rev: Option, - build_id: Option, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Wildcard; - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum SlotOperator { - Eq, - Star, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct SlotName(#[get(method = "name", kind = "deref")] String); - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Slot { - Wildcard, - Equal, - NameEqual { - primary: SlotName, - sub: Option, - }, - Name { - primary: SlotName, - sub: Option, - }, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum UseDepNegate { - Minus, - Exclamation, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum UseDepSign { - Enabled, - Disabled, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum UseDepCondition { - Eq, - Question, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Repo(String); - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct UseDep { - negate: Option, - flag: UseFlag, - sign: Option, - condition: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct Cp { - category: Category, - name: Name, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct Cpv { - category: Category, - name: Name, - version: Version, - slot: Option, -} - -#[derive(Clone, Debug, Get, PartialEq, Eq, Hash)] -pub struct Atom { - blocker: Option, - category: Category, - name: Name, - version: Option<(VersionOperator, Version, Option)>, - slot: Option, - repo: Option, - #[get(kind = "deref")] - usedeps: Vec, -} - -impl Cpv { - #[must_use] - pub fn into_cp(self) -> Cp { - Cp { - name: self.name, - category: self.category, - } - } -} - -impl Atom { - #[must_use] - pub fn version_operator(&self) -> Option { - self.version.clone().map(|(oper, _, _)| oper) - } - - #[must_use] - pub fn into_cp(self) -> Cp { - Cp { - category: self.category, - name: self.name, - } - } - - #[must_use] - pub fn into_cpv(self) -> Option { - match self.version { - Some((_, version, _)) => Some(Cpv { - category: self.category, - name: self.name, - version, - slot: self.slot, - }), - None => None, - } - } -} - -impl VersionNumber { - #[must_use] - pub fn cmp_as_ints(&self, other: &Self) -> Ordering { - let a = self.get().trim_start_matches('0'); - let b = other.get().trim_start_matches('0'); - - a.len().cmp(&b.len()).then_with(|| a.cmp(b)) - } - - #[must_use] - pub fn cmp_as_str(&self, other: &Self) -> Ordering { - if self.get().starts_with('0') || other.get().starts_with('0') { - let a = self.get().trim_end_matches('0'); - let b = other.get().trim_end_matches('0'); - - a.cmp(b) - } else { - self.cmp_as_ints(other) - } - } -} - -impl PartialOrd for BuildId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for BuildId { - fn cmp(&self, other: &Self) -> Ordering { - // build-id may not start with a zero so we dont need to strip them - self.get() - .len() - .cmp(&other.get().len()) - .then_with(|| self.get().cmp(other.get())) - } -} - -impl PartialOrd for VersionSuffix { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VersionSuffix { - fn cmp(&self, other: &Self) -> Ordering { - match &self.kind.cmp(&other.kind) { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => match (&self.number, &other.number) { - (Some(a), Some(b)) => a.cmp_as_ints(b), - (Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal, - (None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal, - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => Ordering::Equal, - }, - } - } -} - -impl PartialOrd for VersionSuffixes { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VersionSuffixes { - fn cmp(&self, other: &Self) -> Ordering { - let mut a = self.get().iter(); - let mut b = other.get().iter(); - - loop { - match (a.next(), b.next()) { - (Some(a), Some(b)) => match a.cmp(b) { - Ordering::Less => break Ordering::Less, - Ordering::Greater => break Ordering::Greater, - Ordering::Equal => (), - }, - (Some(a), None) if matches!(a.kind, VersionSuffixKind::P) => { - break Ordering::Greater; - } - (Some(_), None) => break Ordering::Less, - (None, Some(b)) if matches!(b.kind, VersionSuffixKind::P) => break Ordering::Less, - (None, Some(_)) => break Ordering::Greater, - (None, None) => break Ordering::Equal, - } - } - } -} - -impl PartialOrd for VersionNumbers { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VersionNumbers { - fn cmp(&self, other: &Self) -> Ordering { - match self - .get() - .first() - .unwrap() - .cmp_as_ints(other.get().first().unwrap()) - { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => { - let mut a = self.get().iter().skip(1); - let mut b = other.get().iter().skip(1); - - loop { - match (a.next(), b.next()) { - (Some(a), Some(b)) => match a.cmp_as_str(b) { - Ordering::Less => break Ordering::Less, - Ordering::Greater => break Ordering::Greater, - Ordering::Equal => (), - }, - - (Some(_), None) => break Ordering::Greater, - (None, Some(_)) => break Ordering::Less, - (None, None) => break Ordering::Equal, - } - } - } - } - } -} - -impl PartialOrd for Version { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Version { - fn cmp(&self, other: &Self) -> Ordering { - match self.numbers.cmp(&other.numbers) { - Ordering::Less => return Ordering::Less, - Ordering::Greater => return Ordering::Greater, - Ordering::Equal => (), - } - - match (self.letter, other.letter) { - (Some(a), Some(b)) if a < b => return Ordering::Less, - (Some(a), Some(b)) if a > b => return Ordering::Greater, - (Some(a), Some(b)) if a == b => (), - (Some(_), None) => return Ordering::Greater, - (None, Some(_)) => return Ordering::Less, - (None, None) => (), - _ => unreachable!(), - } - - match self.suffixes.cmp(&other.suffixes) { - Ordering::Less => return Ordering::Less, - Ordering::Greater => return Ordering::Greater, - Ordering::Equal => (), - } - - match (&self.rev, &other.rev) { - (Some(a), Some(b)) => match a.cmp_as_ints(b) { - Ordering::Less => return Ordering::Less, - Ordering::Greater => return Ordering::Greater, - Ordering::Equal => (), - }, - (Some(a), None) if a.get().chars().all(|c| c == '0') => (), - (Some(_), None) => return Ordering::Greater, - (None, Some(b)) if b.get().chars().all(|c| c == '0') => (), - (None, Some(_)) => return Ordering::Less, - (None, None) => (), - } - - match (&self.build_id, &other.build_id) { - (Some(a), Some(b)) => a.cmp(b), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => Ordering::Equal, - } - } -} - -impl PartialOrd for Cpv { - fn partial_cmp(&self, other: &Self) -> Option { - if self.category == other.category && self.name == other.name { - Some(self.version.cmp(&other.version)) - } else { - None - } - } -} - -impl fmt::Display for Blocker { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Weak => write!(f, "!"), - Self::Strong => write!(f, "!!"), - } - } -} - -impl fmt::Display for VersionOperator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Lt => write!(f, "<"), - Self::Gt => write!(f, ">"), - Self::Eq => write!(f, "="), - Self::LtEq => write!(f, "<="), - Self::GtEq => write!(f, ">="), - Self::Roughly => write!(f, "~"), - } - } -} - -impl fmt::Display for Category { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl fmt::Display for Name { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl fmt::Display for VersionNumber { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl fmt::Display for BuildId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.get()) - } -} - -impl fmt::Display for VersionSuffixKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Alpha => write!(f, "alpha"), - Self::Beta => write!(f, "beta"), - Self::Pre => write!(f, "pre"), - Self::Rc => write!(f, "rc"), - Self::P => write!(f, "p"), - } - } -} - -impl fmt::Display for VersionSuffix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.kind)?; - - if let Some(number) = self.number.as_ref() { - write!(f, "{number}")?; - } - - Ok(()) - } -} - -impl fmt::Display for Version { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let numbers = self - .numbers - .get() - .iter() - .map(VersionNumber::get) - .intersperse(".") - .collect::(); - - let suffixes = self - .suffixes - .get() - .iter() - .map(VersionSuffix::to_string) - .intersperse("_".to_string()) - .collect::(); - - write!(f, "{numbers}")?; - - if let Some(letter) = self.letter { - write!(f, "{letter}")?; - } - - if !suffixes.is_empty() { - write!(f, "_{suffixes}")?; - } - - if let Some(rev) = self.rev.as_ref() { - write!(f, "-r{rev}")?; - } - - if let Some(build_id) = self.build_id.as_ref() { - write!(f, "-{build_id}")?; - } - - Ok(()) - } -} - -impl fmt::Display for SlotOperator { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Eq => write!(f, "="), - Self::Star => write!(f, "*"), - } - } -} - -impl fmt::Display for SlotName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl fmt::Display for Slot { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Wildcard => write!(f, "*"), - Self::Equal => { - write!(f, "=") - } - Self::NameEqual { primary, sub } => { - write!(f, "{primary}")?; - - if let Some(sub) = sub { - write!(f, "/{sub}")?; - } - - write!(f, "=") - } - Self::Name { primary, sub } => { - write!(f, "{primary}")?; - - if let Some(sub) = sub { - write!(f, "/{sub}")?; - } - - Ok(()) - } - } - } -} - -impl fmt::Display for UseDepNegate { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Minus => write!(f, "-"), - Self::Exclamation => write!(f, "!"), - } - } -} - -impl fmt::Display for UseDepSign { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Enabled => write!(f, "(+)"), - Self::Disabled => write!(f, "(-)"), - } - } -} - -impl fmt::Display for UseDepCondition { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Eq => write!(f, "="), - Self::Question => write!(f, "?"), - } - } -} - -impl fmt::Display for UseDep { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(negate) = self.negate.as_ref() { - write!(f, "{negate}")?; - } - - write!(f, "{}", self.flag)?; - - if let Some(sign) = self.sign.as_ref() { - write!(f, "{sign}")?; - } - - if let Some(condition) = self.condition.as_ref() { - write!(f, "{condition}")?; - } - - Ok(()) - } -} - -impl fmt::Display for Cp { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}/{}", &self.category, &self.name) - } -} - -impl fmt::Display for Cpv { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}/{}-{}", &self.category, &self.name, &self.version)?; - - if let Some(slot) = self.slot.as_ref() { - write!(f, ":{slot}")?; - } - - Ok(()) - } -} - -impl fmt::Display for Atom { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(blocker) = self.blocker.as_ref() { - write!(f, "{blocker}")?; - } - - if let Some(version_operator) = self.version_operator().as_ref() { - write!(f, "{version_operator}")?; - } - - write!(f, "{}", self.category)?; - write!(f, "/")?; - write!(f, "{}", self.name)?; - - if let Some((_, version, None)) = self.version() { - write!(f, "-{version}")?; - } else if let Some((_, version, Some(_))) = self.version() { - write!(f, "-{version}*")?; - } - - if let Some(slot) = self.slot.as_ref() { - write!(f, ":{slot}")?; - } - - let usedeps = self - .usedeps - .iter() - .map(UseDep::to_string) - .intersperse(",".to_string()) - .collect::(); - - if !usedeps.is_empty() { - write!(f, "[{usedeps}]")?; - } - - Ok(()) - } -} - -#[cfg(test)] -mod test { - use mon::{Parser, input::InputIter}; - - use super::*; - - use crate::Parseable; - - macro_rules! assert_cmp_display { - ($a:expr, $b:expr, $ordering:expr) => { - if $a.cmp(&$b) != $ordering { - panic!("{} ~ {} != {:?}", $a, $b, $ordering) - } - }; - } - - macro_rules! assert_partial_cmp_display { - ($a:expr, $b:expr, $ordering:expr) => { - if $a.partial_cmp(&$b) != $ordering { - panic!("{} ~ {} != {:?}", $a, $b, $ordering) - } - }; - } - - #[test] - fn test_version_display() { - let s = "1.0.0_alpha1_beta1-r1"; - let version = Version::parser().parse_finished(InputIter::new(s)).unwrap(); - - assert_eq!(version.to_string().as_str(), s); - } - - #[test] - fn test_display_atom() { - let s = "!!>=foo/bar-1.0.0v_alpha1_beta1-r1:slot/sub=[a,b,c]"; - let atom = Atom::parser().parse_finished(InputIter::new(s)).unwrap(); - - assert_eq!(atom.to_string().as_str(), s); - } - - #[test] - fn test_version_cmp() { - let versions = [ - ("1.0.1", "1.0", Ordering::Greater), - ("1.0.0", "1.0.0_alpha", Ordering::Greater), - ("1.0.0_alpha", "1.0.0_alpha_p", Ordering::Less), - ("1.0.0-r0", "1.0.0", Ordering::Equal), - ("1.0.0-r0000", "1.0.0", Ordering::Equal), - ("1.0.0-r1-1", "1.0.0-r1-2", Ordering::Less), - ]; - - for (a, b, ordering) in versions.iter().map(|(a, b, ordering)| { - ( - Version::parser().parse_finished(InputIter::new(a)).unwrap(), - Version::parser().parse_finished(InputIter::new(b)).unwrap(), - ordering, - ) - }) { - assert_cmp_display!(a, b, *ordering); - } - } - - #[test] - fn test_cpv_eq() { - let cpvs = [ - ("foo/bar-1", "foo/bar-1", Some(Ordering::Equal)), - ("foo/baz-1", "foo/bar-1", None), - ]; - - for (a, b, ordering) in cpvs.iter().copied().map(|(a, b, ordering)| { - ( - Cpv::parser().parse_finished(InputIter::new(a)).unwrap(), - Cpv::parser().parse_finished(InputIter::new(b)).unwrap(), - ordering, - ) - }) { - assert_partial_cmp_display!(a, b, ordering); - } - } - - #[test] - fn test_version_cmp_letter() { - let a = Version::parser() - .parse_finished(InputIter::new("1.0.0")) - .unwrap(); - let b = Version::parser() - .parse_finished(InputIter::new("1.0.0a")) - .unwrap(); - - assert_cmp_display!(a, b, Ordering::Less); - } - - #[test] - fn test_version_cmp_where_b_has_leading_zeros() { - let a = Version::parser() - .parse_finished(InputIter::new("1.2")) - .unwrap(); - let b = Version::parser() - .parse_finished(InputIter::new("1.054")) - .unwrap(); - - assert_cmp_display!(a, b, Ordering::Greater); - } - - #[test] - fn test_version_has_more_zeros() { - let a = Version::parser() - .parse_finished(InputIter::new("1.0.0")) - .unwrap(); - let b = Version::parser() - .parse_finished(InputIter::new("1.0")) - .unwrap(); - - assert_cmp_display!(a, b, Ordering::Greater); - } - - #[test] - fn test_fuzzer_cases() { - let control = Version::parser() - .parse_finished(InputIter::new("1.2.0a_alpha1_beta2-r1-8")) - .unwrap(); - - #[allow(clippy::single_element_loop)] - for (version_str, expected) in [("1.2.0", Ordering::Greater)] { - let version = Version::parser() - .parse_finished(InputIter::new(version_str)) - .unwrap(); - - assert_cmp_display!(control, version, expected); - } - } -} diff --git a/src/atom/parsers.rs b/src/atom/parsers.rs deleted file mode 100644 index c7ff586..0000000 --- a/src/atom/parsers.rs +++ /dev/null @@ -1,599 +0,0 @@ -use core::option::Option::None; - -use mon::{ - Parser, ParserIter, ascii_alphanumeric, ascii_numeric, ascii_numeric1, eof, r#if, - input::InputIter, one_of, tag, -}; - -use crate::{ - Parseable, - atom::{ - Atom, Blocker, BuildId, Category, Cp, Cpv, Name, Repo, Slot, SlotName, SlotOperator, - UseDep, UseDepCondition, UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers, - VersionOperator, VersionSuffix, VersionSuffixKind, VersionSuffixes, Wildcard, - }, - useflag::UseFlag, -}; - -impl<'a> Parseable<'a, &'a str> for Blocker { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - tag("!!") - .map(|_| Blocker::Strong) - .or(tag("!").map(|_| Blocker::Weak)) - } -} - -impl<'a> Parseable<'a, &'a str> for VersionOperator { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - tag("<=") - .map(|_| VersionOperator::LtEq) - .or(tag(">=").map(|_| VersionOperator::GtEq)) - .or(tag("<").map(|_| VersionOperator::Lt)) - .or(tag(">").map(|_| VersionOperator::Gt)) - .or(tag("=").map(|_| VersionOperator::Eq)) - .or(tag("~").map(|_| VersionOperator::Roughly)) - } -} - -impl<'a> Parseable<'a, &'a str> for VersionNumber { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - ascii_numeric1().map(|output: &str| VersionNumber(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for BuildId { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let start = ascii_numeric().and_not(tag("0")); - let rest = ascii_numeric().repeated().many(); - - start - .and(rest) - .recognize() - .or(tag("0")) - .map(|output: &str| BuildId(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for VersionSuffixKind { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - tag("alpha") - .map(|_| VersionSuffixKind::Alpha) - .or(tag("beta").map(|_| VersionSuffixKind::Beta)) - .or(tag("pre").map(|_| VersionSuffixKind::Pre)) - .or(tag("rc").map(|_| VersionSuffixKind::Rc)) - .or(tag("p").map(|_| VersionSuffixKind::P)) - } -} - -impl<'a> Parseable<'a, &'a str> for VersionSuffix { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - VersionSuffixKind::parser() - .and(VersionNumber::parser().opt()) - .map(|(kind, number)| VersionSuffix { kind, number }) - } -} - -impl<'a> Parseable<'a, &'a str> for VersionNumbers { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - VersionNumber::parser() - .separated_by(tag(".")) - .at_least(1) - .map(VersionNumbers) - } -} - -impl<'a> Parseable<'a, &'a str> for VersionSuffixes { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - VersionSuffix::parser() - .separated_by(tag("_")) - .at_least(1) - .map(VersionSuffixes) - } -} - -impl<'a> Parseable<'a, &'a str> for Version { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let rev = VersionNumber::parser().preceded_by(tag("-r")); - let build_id = BuildId::parser().preceded_by(tag("-")); - - VersionNumbers::parser() - .and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt()) - .and(VersionSuffixes::parser().preceded_by(tag("_")).opt()) - .and(rev.opt()) - .and(build_id.opt()) - .map(|((((numbers, letter), suffixes), rev), build_id)| Version { - numbers, - letter, - suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())), - rev, - build_id, - }) - } -} - -impl<'a> Parseable<'a, &'a str> for Category { - 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| Category(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for Name { - 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())) - .or(one_of("-".chars()).and_not( - Version::parser() - .preceded_by(tag("-")) - .followed_by(ascii_alphanumeric().or(one_of("_+-".chars())).not()), - )) - .repeated() - .many(); - - let verify = ascii_alphanumeric() - .or(one_of("_+".chars())) - .or(one_of("-".chars()) - .and_not(Version::parser().preceded_by(tag("-")).followed_by(eof()))) - .repeated() - .many(); - - start() - .and(rest) - .recognize() - .verify_output(move |output: &&str| { - verify.check_finished(InputIter::new(*output)).is_ok() - }) - .map(|output: &str| Name(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for SlotOperator { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - tag("=") - .map(|_| SlotOperator::Eq) - .or(tag("*").map(|_| SlotOperator::Star)) - } -} - -impl<'a> Parseable<'a, &'a str> for SlotName { - 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| SlotName(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for Slot { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let wildcard = tag("*").map(|_| Slot::Wildcard); - let equal = tag("=").map(|_| Slot::Equal); - let name_equal = SlotName::parser() - .and(SlotName::parser().preceded_by(tag("/")).opt()) - .followed_by(tag("=")) - .map(|(primary, sub)| Slot::NameEqual { primary, sub }); - let name = SlotName::parser() - .and(SlotName::parser().preceded_by(tag("/")).opt()) - .map(|(primary, sub)| Self::Name { primary, sub }); - - wildcard.or(equal).or(name_equal).or(name) - } -} - -impl<'a> Parseable<'a, &'a str> for UseDepSign { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - tag("(-)") - .map(|_| UseDepSign::Disabled) - .or(tag("(+)").map(|_| UseDepSign::Enabled)) - } -} - -impl<'a> Parseable<'a, &'a str> for Repo { - 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() - .verify_output(move |output: &&str| { - Name::parser() - .check_finished(InputIter::new(*output)) - .is_ok() - }) - .map(|output: &str| Repo(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for UseDep { - type Parser = impl Parser<&'a str, Output = Self>; - - #[allow(clippy::many_single_char_names)] - fn parser() -> Self::Parser { - let a = UseFlag::parser() - .and(UseDepSign::parser().opt()) - .preceded_by(tag("-")) - .map(|(flag, sign)| UseDep { - negate: Some(UseDepNegate::Minus), - flag, - sign, - condition: None, - }); - - let b = UseFlag::parser() - .and(UseDepSign::parser().opt()) - .preceded_by(tag("!")) - .followed_by(tag("?")) - .map(|(flag, sign)| UseDep { - negate: Some(UseDepNegate::Exclamation), - flag, - sign, - condition: Some(UseDepCondition::Question), - }); - - let c = UseFlag::parser() - .and(UseDepSign::parser().opt()) - .followed_by(tag("?")) - .map(|(flag, sign)| UseDep { - negate: None, - flag, - sign, - condition: Some(UseDepCondition::Question), - }); - - let d = UseFlag::parser() - .and(UseDepSign::parser().opt()) - .preceded_by(tag("!")) - .followed_by(tag("=")) - .map(|(flag, sign)| UseDep { - negate: Some(UseDepNegate::Exclamation), - flag, - sign, - condition: Some(UseDepCondition::Eq), - }); - - let e = UseFlag::parser() - .and(UseDepSign::parser().opt()) - .followed_by(tag("=")) - .map(|(flag, sign)| UseDep { - negate: None, - flag, - sign, - condition: Some(UseDepCondition::Eq), - }); - - let f = UseFlag::parser() - .and(UseDepSign::parser().opt()) - .map(|(flag, sign)| UseDep { - negate: None, - flag, - sign, - condition: None, - }); - - a.or(b).or(c).or(d).or(e).or(f) - } -} - -impl<'a> Parseable<'a, &'a str> for Atom { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let usedeps = || { - UseDep::parser() - .separated_by(tag(",")) - .at_least(1) - .delimited_by(tag("["), tag("]")) - .opt() - }; - - let without_version = Blocker::parser() - .opt() - .and(Category::parser()) - .and(Name::parser().preceded_by(tag("/"))) - .and(Slot::parser().preceded_by(tag(":")).opt()) - .and(Repo::parser().preceded_by(tag("::")).opt()) - .and(usedeps()) - .map( - |(((((blocker, category), name), slot), repo), usedeps)| Atom { - blocker, - category, - name, - version: None, - slot, - repo, - usedeps: usedeps.unwrap_or(Vec::new()), - }, - ); - - let with_version = Blocker::parser() - .opt() - .and(VersionOperator::parser()) - .and(Category::parser()) - .and(Name::parser().preceded_by(tag("/"))) - .and(Version::parser().preceded_by(tag("-"))) - .and(tag("*").map(|_| Wildcard).opt()) - .and(Slot::parser().preceded_by(tag(":")).opt()) - .and(Repo::parser().preceded_by(tag("::")).opt()) - .and(usedeps()) - .verify_output( - |((((((((_, version_operator), _), _), version), star), _), _), _)| { - matches!( - (version_operator, star), - (VersionOperator::Eq, Some(_) | None) | (_, None) - ) && matches!((version.build_id(), star), (Some(_), None) | (None, _)) - }, - ) - .map( - |( - ( - ((((((blocker, version_operator), category), name), version), star), slot), - repo, - ), - usedeps, - )| { - Atom { - blocker, - category, - name, - version: Some((version_operator, version, star)), - slot, - repo, - usedeps: usedeps.unwrap_or(Vec::new()), - } - }, - ); - - with_version.or(without_version) - } -} - -impl<'a> Parseable<'a, &'a str> for Cp { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - Category::parser() - .and(Name::parser().preceded_by(tag("/"))) - .map(|(category, name)| Cp { category, name }) - } -} - -impl<'a> Parseable<'a, &'a str> for Cpv { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - Category::parser() - .and(Name::parser().preceded_by(tag("/"))) - .and(Version::parser().preceded_by(tag("-"))) - .and(Slot::parser().preceded_by(tag(":")).opt()) - .map(|(((category, name), version), slot)| Cpv { - category, - name, - version, - slot, - }) - } -} - -#[cfg(test)] -mod test { - - use mon::input::InputIter; - - use super::*; - - #[test] - fn test_version() { - let it = InputIter::new("1.0.0v_alpha1_beta1-r1"); - - Version::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_name() { - let it = InputIter::new("foo-1-bar-1.0.0"); - - match Name::parser().parse(it) { - Ok((_, output)) => { - assert_eq!(output.0.as_str(), "foo-1-bar"); - } - _ => unreachable!(), - } - } - - #[test] - fn test_atom() { - let it = InputIter::new( - "!!>=cat/pkg-1-foo-1.0.0v_alpha1_p20250326-r1:primary/sub=[use,use=,!use=,use?,!use?,-use,use(+),use(-)]", - ); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_cursed_atom() { - let it = InputIter::new( - "!!>=_.+-0-/_-test-T-123_beta1_-4a-6+-_p--1.00.02b_alpha3_pre_p4-r5:slot/_-+6-9=[test(+),test(-)]", - ); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_atom_with_star_in_non_empty_slot() { - let it = InputIter::new("foo/bar:*/subslot"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_invalid_usedep() { - let it = InputIter::new("foo-bar:slot/sub=[!use]"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_empty_slot() { - let it = InputIter::new("=dev-ml/uucp-17*:"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_usedep_with_underscore() { - let it = InputIter::new("foo/bar[use_dep]"); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_version_with_uppercase_letter() { - let it = InputIter::new("=foo/bar-1.0.0V"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_version_with_version_operator_without_version() { - let it = InputIter::new("=foo/bar"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_version_with_version_without_version_operator() { - let it = InputIter::new("foo/bar-1.0.0"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_atom_with_eq_version_operator() { - let it = InputIter::new("=foo/bar-1.0.0"); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_atom_with_star_in_version() { - let it = InputIter::new("=foo/bar-1.2*"); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_atom_with_star_in_version_without_eq_version_operator() { - let it = InputIter::new(">=foo/bar-1.2*"); - - assert!(Atom::parser().check_finished(it).is_err()); - } - - #[test] - fn test_atom_with_trailing_dash_and_letter() { - let it = InputIter::new("dev-db/mysql-connector-c"); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_cpv_with_slot() { - let it = InputIter::new("foo/bar-1.0:slot/sub="); - - Cpv::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_cpv_without_version_but_trailing_almost_version() { - let it = InputIter::new("dev-perl/mod-p-2.3_"); - - assert!(Cpv::parser().parse_finished(it).is_err()); - } - - #[test] - fn test_empty_slot_with_operator() { - let it = InputIter::new("foo/bar:="); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_with_repo() { - let it = InputIter::new("=foo/bar-1.0.0:slot/sub=::gentoo[a,b,c]"); - - Atom::parser().check_finished(it).unwrap(); - } - - #[test] - fn test_against_fuzzer_false_positives() { - let atoms = [ - "media-libs/libsdl2[haptitick(+),sound(+)vd,eio(+)]", - "=kde-frameworks/kcodecs-6.19*86", - "=dev-ml/stdio-0.17*t:=[ocamlopt?]", - ">=dev-libs/libgee-0-8.5:0..8=", - "=kde-frameworks/kcrash-2.16.0:6*", - "0-f/merreka+m::k+", - "iev-a/h:/n=", - "=dev-ml/stdio-0-17*:=[ocamlopt?]", - ]; - - for atom in atoms { - assert!( - Atom::parser().check_finished(InputIter::new(atom)).is_err(), - "{atom}" - ); - } - } -} diff --git a/src/lib.rs b/src/lib.rs index b7b01a7..78fe580 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,81 +11,6 @@ //! - vdb reader //! - sourcing ebuilds with bash //! - -#![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 mon::{ - Parser, - input::{Input, InputIter}, -}; - -pub trait Parseable<'a, I: Input + 'a> { - type Parser: Parser; - - fn parser() -> Self::Parser; - - fn parse(input: I) -> Result - where - Self: Sized, - { - Self::parser() - .parse_finished(InputIter::new(input)) - .map_err(|e| e.rest()) - } -} - -/// Strongly typed atom and cpv representations. -/// -/// Create atoms from parsers: -/// ``` -/// use gentoo_utils::{Parseable, atom::Atom}; -/// -/// let emacs = Atom::parse("=app-editors/emacs-31.0-r1") -/// .expect("failed to parse atom"); -/// -/// assert_eq!(emacs.to_string(), "=app-editors/emacs-31.0-r1"); -/// ```` -/// -/// Compare versions: -/// ``` -/// use gentoo_utils::{Parseable, atom::Cpv}; -/// -/// let a = Cpv::parse("foo/bar-1.0").unwrap(); -/// let b = Cpv::parse("foo/bar-2.0").unwrap(); -/// -/// assert!(a < b); -/// ``` -pub mod atom; - -/// Access to repos and ebuilds. -/// -/// ``` -/// use gentoo_utils::repo::Repo; -/// -/// let repo = Repo::new("/var/db/repos/gentoo"); -/// -/// for result in repo.categories().expect("failed to read categories") { -/// let category = result.expect("failed to read category"); -/// -/// for result in category.ebuilds().expect("failed to read ebuilds") { -/// let ebuild = result.expect("failed to read ebuild"); -/// -/// println!( -/// "{}-{}: {}", -/// ebuild.name(), -/// ebuild.version(), -/// ebuild.description().clone().unwrap_or("no description available".to_string()) -/// ); -/// } -/// } -/// -/// ``` -pub mod repo; -pub mod useflag; +pub use atom; +pub use useflag; +pub use repo; diff --git a/src/meson.build b/src/meson.build index 33a09de..8b7f4c9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1 @@ sources += files('lib.rs') - -subdir('atom') -subdir('repo') -subdir('useflag') diff --git a/src/repo/ebuild/meson.build b/src/repo/ebuild/meson.build deleted file mode 100644 index a7331a8..0000000 --- a/src/repo/ebuild/meson.build +++ /dev/null @@ -1 +0,0 @@ -sources += files('mod.rs', 'parsers.rs') diff --git a/src/repo/ebuild/mod.rs b/src/repo/ebuild/mod.rs deleted file mode 100644 index 3f52db9..0000000 --- a/src/repo/ebuild/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index c80cdf2..0000000 --- a/src/repo/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, - 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/meson.build b/src/repo/meson.build deleted file mode 100644 index c1be7a7..0000000 --- a/src/repo/meson.build +++ /dev/null @@ -1,3 +0,0 @@ -sources += files('mod.rs') - -subdir('ebuild') diff --git a/src/repo/mod.rs b/src/repo/mod.rs deleted file mode 100644 index eb68839..0000000 --- a/src/repo/mod.rs +++ /dev/null @@ -1,325 +0,0 @@ -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}, - 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 { - 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)), - }, - Err(e) => break Some(Err(Error::ReadDir(self.0.clone(), e))), - } - } - } -} - -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/meson.build b/src/useflag/meson.build deleted file mode 100644 index a7331a8..0000000 --- a/src/useflag/meson.build +++ /dev/null @@ -1 +0,0 @@ -sources += files('mod.rs', 'parsers.rs') diff --git a/src/useflag/mod.rs b/src/useflag/mod.rs deleted file mode 100644 index c367b1a..0000000 --- a/src/useflag/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use core::fmt; - -use get::Get; - -mod parsers; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct UseFlag(#[get(method = "name", kind = "deref")] String); - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Get)] -pub struct IUseFlag { - default: bool, - flag: UseFlag, -} - -impl fmt::Display for UseFlag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/src/useflag/parsers.rs b/src/useflag/parsers.rs deleted file mode 100644 index ca5929d..0000000 --- a/src/useflag/parsers.rs +++ /dev/null @@ -1,40 +0,0 @@ -use mon::{Parser, ParserIter, ascii_alphanumeric, one_of, tag}; - -use crate::{ - Parseable, - useflag::{IUseFlag, UseFlag}, -}; - -impl<'a> Parseable<'a, &'a str> for UseFlag { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - let start = ascii_alphanumeric(); - let rest = ascii_alphanumeric() - .or(one_of("+_@-".chars())) - .repeated() - .many(); - - start - .and(rest) - .recognize() - .map(|output: &str| UseFlag(output.to_string())) - } -} - -impl<'a> Parseable<'a, &'a str> for IUseFlag { - type Parser = impl Parser<&'a str, Output = Self>; - - fn parser() -> Self::Parser { - UseFlag::parser() - .preceded_by(tag("+")) - .map(|flag| IUseFlag { - default: true, - flag, - }) - .or(UseFlag::parser().map(|flag| IUseFlag { - default: false, - flag, - })) - } -} -- cgit v1.2.3