use core::{ fmt::{self}, option::Option, }; use std::cmp::Ordering; use crate::useflag::UseFlag; use get::Get; use itertools::Itertools; 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, Eq, LtEq, GtEq, Roughly, } #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct Category(#[get(method = "get", kind = "deref")] String); #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct Name(#[get(method = "get", kind = "deref")] String); #[derive(Clone, Debug, Get)] pub struct VersionNumber(#[get(method = "get", kind = "deref")] String); #[derive(Debug, Clone, Get)] struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec); #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum VersionSuffixKind { Alpha, Beta, Pre, Rc, P, } #[derive(Clone, Debug, Get)] pub struct VersionSuffix { kind: VersionSuffixKind, number: Option, } #[derive(Debug, Clone, Get)] pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec); #[derive(Clone, Debug, Get)] pub struct Version { numbers: VersionNumbers, letter: Option, suffixes: VersionSuffixes, rev: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SlotOperator { Eq, Star, } #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct SlotName(#[get(method = "name", kind = "deref")] String); #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct Slot { slot: Option, sub: Option, operator: Option, } #[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, flag: UseFlag, sign: Option, condition: Option, } #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct Cp { category: Category, name: Name, } #[derive(Clone, Debug, PartialEq, Eq, Get)] pub struct Cpv { category: Category, name: Name, version: Version, slot: Option, } #[derive(Clone, Debug, Get, PartialEq, Eq)] pub struct Atom { blocker: Option, category: Category, name: Name, version: Option<(VersionOperator, Version)>, slot: Option, #[get(kind = "deref")] usedeps: Vec, } impl Atom { pub fn version_operator(&self) -> Option { match self.version { Some((operator, _)) => Some(operator), None => None, } } } impl PartialEq for VersionSuffix { fn eq(&self, other: &Self) -> bool { self.kind == other.kind && match dbg!((&self.number, &other.number)) { (Some(a), Some(b)) => a.0 == b.0, (Some(a), None) if a.get().chars().all(|c| c == '0') => true, (None, Some(b)) if b.get().chars().all(|c| c == '0') => true, (Some(_), None) => false, (None, Some(_)) => false, (None, None) => true, } } } impl Eq for VersionSuffix {} 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.0.parse::() .unwrap() .cmp(&b.0.parse::().unwrap()) } (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 PartialEq for VersionSuffixes { fn eq(&self, other: &Self) -> bool { let mut a = self.get().iter(); let mut b = other.get().iter(); loop { match (a.next(), b.next()) { (Some(a), Some(b)) if a == b => continue, (Some(_), Some(_)) => break false, (None, None) => break true, _ => break false, } } } } impl Eq for VersionSuffixes {} 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 dbg!((a.next(), b.next())) { (Some(a), Some(b)) => match a.cmp(b) { Ordering::Less => break Ordering::Less, Ordering::Greater => break Ordering::Greater, Ordering::Equal => continue, }, (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 PartialEq for VersionNumbers { fn eq(&self, other: &Self) -> bool { self.get().first().unwrap().get().parse::().unwrap() == other.get().first().unwrap().get().parse().unwrap() && { 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)) if a.get().starts_with("0") => { let a = a.get().trim_end_matches("0"); let b = b.get().trim_end_matches("0"); if a != b { break false; } } (Some(a), Some(b)) => { if a.get().parse::().unwrap() != b.get().parse::().unwrap() { break false; } } (Some(a), None) if a.get().chars().all(|c| c == '0') => continue, (None, Some(b)) if b.get().chars().all(|c| c == '0') => continue, (None, None) => break true, _ => break false, } } } } } impl Eq for VersionNumbers {} 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() .get() .parse::() .unwrap() .cmp(&other.get().first().unwrap().get().parse::().unwrap()) { Ordering::Less => return Ordering::Less, Ordering::Greater => return 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)) if a.get().starts_with("0") => { let a = a.get().trim_end_matches("0"); let b = b.get().trim_end_matches("0"); match a.cmp(b) { Ordering::Less => break Ordering::Less, Ordering::Greater => break Ordering::Greater, Ordering::Equal => continue, } } (Some(a), Some(b)) => match a .get() .parse::() .unwrap() .cmp(&b.get().parse::().unwrap()) { Ordering::Less => break Ordering::Less, Ordering::Greater => break Ordering::Greater, Ordering::Equal => continue, }, (Some(a), None) if a.get().chars().all(|c| c == '0') => continue, (None, Some(b)) if b.get().chars().all(|c| c == '0') => continue, (Some(_), None) => break Ordering::Greater, (None, Some(_)) => break Ordering::Less, (None, None) => break Ordering::Equal, } } } } } } impl PartialEq for Version { fn eq(&self, other: &Self) -> bool { self.numbers == other.numbers && self.suffixes == other.suffixes && match (&self.rev, &other.rev) { (Some(a), Some(b)) => { a.get().parse::().unwrap() == b.get().parse::().unwrap() } (Some(a), None) if a.get().chars().all(|c| c == '0') => true, (Some(_), None) => false, (None, Some(b)) if b.get().chars().all(|c| c == '0') => true, (None, Some(_)) => false, (None, None) => true, } } } impl Eq for Version {} 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 => Ordering::Less, Ordering::Greater => Ordering::Greater, Ordering::Equal => match self.suffixes.cmp(&other.suffixes) { Ordering::Less => Ordering::Less, Ordering::Greater => Ordering::Greater, Ordering::Equal => match (&self.rev, &other.rev) { (Some(a), Some(b)) => a .get() .parse::() .unwrap() .cmp(&b.get().parse().unwrap()), (Some(a), None) if a.get().chars().all(|c| c == '0') => Ordering::Equal, (Some(_), None) => Ordering::Greater, (None, Some(b)) if b.get().chars().all(|c| c == '0') => Ordering::Equal, (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 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(|n| n.get()) .intersperse(".") .collect::(); let suffixes = self .suffixes .get() .iter() .map(|s| s.to_string()) .intersperse("_".to_string()) .collect::(); write!(f, "{}", numbers)?; if let Some(letter) = self.letter { write!(f, "{letter}")?; } if suffixes.len() > 0 { write!(f, "_{}", suffixes)?; } if let Some(rev) = self.rev.as_ref() { write!(f, "-r{rev}")?; } 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 { if let Some(slot) = self.slot.as_ref() { write!(f, "{slot}")?; } if let Some(sub) = self.sub.as_ref() { write!(f, "/{sub}")?; } if let Some(operator) = self.operator.as_ref() { write!(f, "{operator}")?; } 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)) = self.version.as_ref() { write!(f, "-{version}")?; } if let Some(slot) = self.slot.as_ref() { write!(f, ":{slot}")?; } let usedeps = self .usedeps .iter() .map(|u| u.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_eq_display { ($a:expr, $b:expr) => { if $a != $b { panic!("{} != {}", $a, $b); } }; } 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_suffix_eq() { let a = VersionSuffix::parser() .parse_finished(InputIter::new("alpha0")) .unwrap(); let b = VersionSuffix::parser() .parse_finished(InputIter::new("alpha")) .unwrap(); assert_eq_display!(a, b); } #[test] fn test_version_eq() { let versions = [ ("1", "1"), ("1", "1.0.0"), ("1.0", "1.0.0"), ("1.0.0_alpha0", "1.0.0_alpha"), ("1.0.0", "1.0.0-r0"), ]; for (a, b) in versions.map(|(a, b)| { ( Version::parser().parse_finished(InputIter::new(a)).unwrap(), Version::parser().parse_finished(InputIter::new(b)).unwrap(), ) }) { assert_eq_display!(a, b); } } #[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), ]; 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); } } }