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()); } }