use core::option::Option::None; use mon::{Parser, ParserIter, alphanumeric, r#if, numeric1, one_of, tag}; use crate::{ Parseable, atom::{ Atom, Blocker, Category, Cp, Cpv, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition, UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers, VersionOperator, VersionSuffix, VersionSuffixKind, VersionSuffixes, }, 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 { numeric1().map(|output: &str| VersionNumber(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() .followed_by(tag("*").opt()) .recognize() .map(|output: &str| VersionNumber(output.to_string())) .separated_by(tag(".")) .at_least(1) .map(|numbers| VersionNumbers(numbers)) } } 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(|suffixes| VersionSuffixes(suffixes)) } } 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")); 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()) .map(|(((numbers, letter), suffixes), rev)| Version { numbers, letter, suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())), rev, }) } } impl<'a> Parseable<'a, &'a str> for Category { type Parser = impl Parser<&'a str, Output = Self>; fn parser() -> Self::Parser { let start = alphanumeric().or(one_of("_".chars())); let rest = 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 = alphanumeric().or(one_of("_".chars())); let rest = alphanumeric() .or(one_of("_+".chars())) .or(one_of("-".chars()).and_not( Version::parser() .preceded_by(tag("-")) .followed_by(alphanumeric().or(one_of("_+-".chars())).not()), )) .repeated() .many(); start .and(rest) .recognize() .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 = alphanumeric().or(one_of("_".chars())); let rest = 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 { SlotName::parser() .opt() .and(SlotName::parser().preceded_by(tag("/")).opt()) .and(SlotOperator::parser().opt()) .map(|((slot, sub), operator)| Slot { slot, sub, operator, }) } } 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 UseDep { type Parser = impl Parser<&'a str, Output = Self>; 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(",")) .many() .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(usedeps()) .map(|((((blocker, category), name), slot), usedeps)| Atom { blocker, category, name, version: None, slot, 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(Slot::parser().preceded_by(tag(":")).opt()) .and(usedeps()) .map( |((((((blocker, version_operator), category), name), version), slot), usedeps)| { Atom { blocker, category, name, version: Some((version_operator, version)), slot, usedeps: usedeps.unwrap_or(Vec::new()), } }, ) .verify_output(|atom| match &atom.version { Some((VersionOperator::Eq, _)) => true, Some((_, version)) if !version .numbers() .get() .iter() .any(|number| number.get().contains("*")) => { true } _ => false, }); 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("foo/bar:="); Atom::parser().check_finished(it).unwrap(); } #[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()); } }