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, build_id: Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Wildcard; #[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)] pub enum Slot { Wildcard, Equal { primary: Option, sub: Option, }, Name { primary: SlotName, sub: 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)] pub struct Repo(String); #[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, 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 PartialEq for VersionSuffix { fn eq(&self, other: &Self) -> bool { self.kind == other.kind && match (&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) | (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.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 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 => (), (Some(_) | None, Some(_)) | (Some(_), None) => break false, (None, None) => break true, } } } } 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 (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 PartialEq for VersionNumbers { fn eq(&self, other: &Self) -> bool { self.get() .first() .unwrap() .cmp_as_ints(other.get().first().unwrap()) == 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)) => { let Ordering::Equal = a.cmp_as_str(b) else { return false; }; } (Some(a), None) if a.get().chars().all(|c| c == '0') => (), (None, Some(b)) if b.get().chars().all(|c| c == '0') => (), (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() .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 PartialEq for Version { fn eq(&self, other: &Self) -> bool { self.numbers == other.numbers && self.suffixes == other.suffixes && self.letter == other.letter && match (&self.rev, &other.rev) { (Some(a), Some(b)) => matches!(a.cmp_as_ints(b), Ordering::Equal), (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) | (None, Some(_)) => false, (None, None) => true, } && match (&self.build_id, &other.build_id) { (Some(a), Some(b)) => matches!(a.cmp_as_ints(b), Ordering::Equal), (Some(_), None) | (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 => 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_as_ints(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 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}")?; } 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 { primary, sub } => { if let Some(primary) = primary { 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_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), ("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(); 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); } } }