summaryrefslogtreecommitdiff
path: root/src/atom
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-10-23 00:52:35 -0400
committerJohn Turner <jturner.usa@gmail.com>2025-10-23 21:19:47 -0400
commit2e7d8cfbb92361b6c35f8f9a567c0517139cfb51 (patch)
treee6ce91d8b138f597db408e18318bd768d3e92cc3 /src/atom
parent6e4b45027e993f805346809f22d7a209bb477d2f (diff)
downloadgentoo-utils-2e7d8cfbb92361b6c35f8f9a567c0517139cfb51.tar.gz
impl atom parsing
Diffstat (limited to 'src/atom')
-rw-r--r--src/atom/mod.rs106
-rw-r--r--src/atom/parsers.rs227
2 files changed, 333 insertions, 0 deletions
diff --git a/src/atom/mod.rs b/src/atom/mod.rs
new file mode 100644
index 0000000..f5884bc
--- /dev/null
+++ b/src/atom/mod.rs
@@ -0,0 +1,106 @@
+use core::option::Option;
+
+use crate::useflag::UseFlag;
+use get::Get;
+
+pub mod parsers;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Blocker {
+ Weak,
+ Strong,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum VersionOperator {
+ Lt,
+ Gt,
+ LtEq,
+ GtEq,
+ Roughly,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Get)]
+pub struct Category(#[get(method = "get")] String);
+
+#[derive(Clone, Debug, PartialEq, Eq, Get)]
+pub struct Name(#[get(method = "get")] String);
+
+#[derive(Clone, Debug, Get)]
+pub struct VersionNumber(#[get(method = "get")] String);
+
+#[derive(Clone, Copy, Debug)]
+pub enum VersionSuffixKind {
+ Alpha,
+ Beta,
+ Pre,
+ Rc,
+ P,
+}
+
+#[derive(Clone, Debug, Get)]
+pub struct VersionSuffix {
+ kind: VersionSuffixKind,
+ number: Option<VersionNumber>,
+}
+
+#[derive(Clone, Debug, Get)]
+pub struct Version {
+ numbers: Vec<VersionNumber>,
+ letter: Option<char>,
+ suffixes: Vec<VersionSuffix>,
+ rev: Option<VersionNumber>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum SlotOperator {
+ Eq,
+ Star,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Get)]
+pub struct SlotName(#[get(method = "name")] String);
+
+#[derive(Clone, Debug, PartialEq, Eq, Get)]
+pub struct Slot {
+ slot: SlotName,
+ sub: Option<SlotName>,
+ operator: Option<SlotOperator>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum UseDepNegate {
+ Minus,
+ Exclamation,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum UseDepSign {
+ Enabled,
+ Disabled,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum UseDepCondition {
+ Eq,
+ Question,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Get)]
+pub struct UseDep {
+ negate: Option<UseDepNegate>,
+ flag: UseFlag,
+ sign: Option<UseDepSign>,
+ condition: Option<UseDepCondition>,
+}
+
+#[derive(Clone, Debug, Get)]
+pub struct Atom {
+ blocker: Option<Blocker>,
+ version_operator: Option<VersionOperator>,
+ category: Category,
+ name: Name,
+ version: Option<Version>,
+ slot: Option<Slot>,
+ usedeps: Vec<UseDep>,
+}
diff --git a/src/atom/parsers.rs b/src/atom/parsers.rs
new file mode 100644
index 0000000..7b2b096
--- /dev/null
+++ b/src/atom/parsers.rs
@@ -0,0 +1,227 @@
+use mon::{Parser, r#if, not, numeric1, one_of, opt, tag, take_while};
+
+use crate::{
+ atom::{
+ Atom, Blocker, Category, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition,
+ UseDepNegate, UseDepSign, Version, VersionNumber, VersionOperator, VersionSuffix,
+ VersionSuffixKind,
+ },
+ useflag::parsers::useflag,
+};
+
+pub fn blocker<'a>() -> impl Parser<&'a str, Output = Blocker> {
+ tag("!!")
+ .map(|_| Blocker::Strong)
+ .or(tag("!").map(|_| Blocker::Weak))
+}
+
+pub fn version_operator<'a>() -> impl Parser<&'a str, Output = VersionOperator> {
+ tag("<=")
+ .map(|_| VersionOperator::LtEq)
+ .or(tag(">=").map(|_| VersionOperator::GtEq))
+ .or(tag("<").map(|_| VersionOperator::Lt))
+ .or(tag(">").map(|_| VersionOperator::Gt))
+ .or(tag("~").map(|_| VersionOperator::Roughly))
+}
+
+pub fn version_number<'a>() -> impl Parser<&'a str, Output = VersionNumber> {
+ numeric1().map(|output: &str| VersionNumber(output.to_string()))
+}
+
+pub fn version_suffix_kind<'a>() -> impl Parser<&'a str, Output = VersionSuffixKind> {
+ 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))
+}
+
+pub fn version_suffix<'a>() -> impl Parser<&'a str, Output = VersionSuffix> {
+ version_suffix_kind()
+ .and(opt(version_number()))
+ .map(|(kind, number)| VersionSuffix { kind, number })
+}
+
+pub fn version<'a>() -> impl Parser<&'a str, Output = Version> {
+ let numbers = version_number().separated_list1(tag("."));
+ let suffixes = version_suffix().separated_list(tag("_"));
+ let rev = version_number().preceded_by(tag("-r"));
+
+ numbers
+ .and(opt(r#if(|c: &char| c.is_ascii_alphabetic())))
+ .and(opt(suffixes.preceded_by(tag("_"))))
+ .and(opt(rev))
+ .map(|(((numbers, letter), suffixes), rev)| Version {
+ numbers,
+ letter,
+ suffixes: suffixes.unwrap_or(Vec::new()),
+ rev,
+ })
+}
+
+pub fn category<'a>() -> impl Parser<&'a str, Output = Category> {
+ let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
+ let rest = take_while(r#if(|c: &char| {
+ c.is_ascii_alphanumeric() || "+_.-".contains(*c)
+ }));
+
+ start
+ .and(rest)
+ .recognize()
+ .map(|output: &str| Category(output.to_string()))
+}
+
+pub fn name<'a>() -> impl Parser<&'a str, Output = Name> {
+ let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
+ let rest = take_while(
+ r#if(|c: &char| c.is_ascii_alphanumeric() || "_+".contains(*c)).or(one_of("-".chars())
+ .and_not(
+ version()
+ .preceded_by(tag("-"))
+ .followed_by(not(r#if(|c: &char| {
+ c.is_ascii_alphanumeric() || "_+-".contains(*c)
+ }))),
+ )),
+ );
+
+ start
+ .and(rest)
+ .recognize()
+ .map(|output: &str| Name(output.to_string()))
+}
+
+pub fn slot_operator<'a>() -> impl Parser<&'a str, Output = SlotOperator> {
+ tag("=")
+ .map(|_| SlotOperator::Eq)
+ .or(tag("*").map(|_| SlotOperator::Star))
+}
+
+pub fn slotname<'a>() -> impl Parser<&'a str, Output = SlotName> {
+ let start = r#if(|c: &char| c.is_ascii_alphanumeric() || *c == '_');
+ let rest = take_while(r#if(|c: &char| {
+ c.is_ascii_alphanumeric() || "+_.-".contains(*c)
+ }));
+
+ start
+ .and(rest)
+ .recognize()
+ .map(|output: &str| SlotName(output.to_string()))
+}
+
+pub fn slot<'a>() -> impl Parser<&'a str, Output = Slot> {
+ slotname()
+ .and(opt(slotname().preceded_by(tag("/"))))
+ .and(opt(slot_operator()))
+ .map(|((slot, sub), operator)| Slot {
+ slot,
+ sub,
+ operator,
+ })
+}
+
+pub fn usedep_negate<'a>() -> impl Parser<&'a str, Output = UseDepNegate> {
+ tag("-")
+ .map(|_| UseDepNegate::Minus)
+ .or(tag("!").map(|_| UseDepNegate::Exclamation))
+}
+
+pub fn usedep_sign<'a>() -> impl Parser<&'a str, Output = UseDepSign> {
+ tag("(-)")
+ .map(|_| UseDepSign::Disabled)
+ .or(tag("(+)").map(|_| UseDepSign::Enabled))
+}
+
+pub fn usedep_condition<'a>() -> impl Parser<&'a str, Output = UseDepCondition> {
+ tag("=")
+ .map(|_| UseDepCondition::Eq)
+ .or(tag("?").map(|_| UseDepCondition::Question))
+}
+
+pub fn usedep<'a>() -> impl Parser<&'a str, Output = UseDep> {
+ opt(usedep_negate())
+ .and(useflag())
+ .and(opt(usedep_sign()))
+ .and(opt(usedep_condition()))
+ .map(|(((negate, flag), sign), condition)| UseDep {
+ negate,
+ flag,
+ sign,
+ condition,
+ })
+}
+
+pub fn atom<'a>() -> impl Parser<&'a str, Output = Atom> {
+ opt(blocker())
+ .and(opt(version_operator()))
+ .and(category())
+ .and(name().preceded_by(tag("/")))
+ .and(opt(version().preceded_by(tag("-"))))
+ .and(opt(slot().preceded_by(tag(":"))))
+ .and(opt(usedep()
+ .separated_list(tag(","))
+ .delimited_by(tag("["), tag("]"))))
+ .map(
+ |((((((blocker, version_operator), category), name), version), slot), usedeps)| Atom {
+ blocker,
+ version_operator,
+ category,
+ name,
+ version,
+ slot,
+ usedeps: usedeps.unwrap_or(Vec::new()),
+ },
+ )
+}
+
+#[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().check_finished(it).unwrap();
+ }
+
+ #[test]
+ fn test_name() {
+ let it = InputIter::new("foo-1-bar-1.0.0");
+
+ match name().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=[!a(+),-b(-)=,c?]",
+ );
+
+ atom().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().check_finished(it).unwrap();
+ }
+
+ #[test]
+ fn test_atom_with_star_in_non_empty_slot() {
+ let it = InputIter::new("foo/bar-1.0.0:*/subslot");
+
+ assert!(atom().check_finished(it).is_err());
+ }
+}