summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Turner <jturner.usa@gmail.com>2025-11-13 18:00:15 +0000
committerJohn Turner <jturner.usa@gmail.com>2025-11-13 18:00:15 +0000
commitf4a45717d2bc952a545f7cee28e941a65744604b (patch)
tree6a45f1bb889871a9474141fdef11fcbee1e11895 /src
parent3b7a662598146df9706d8864cd4135696496b8e1 (diff)
downloadgentoo-utils-f4a45717d2bc952a545f7cee28e941a65744604b.tar.gz
impl version comparison algorithm
Diffstat (limited to 'src')
-rw-r--r--src/atom/mod.rs321
-rw-r--r--src/atom/parsers.rs38
2 files changed, 346 insertions, 13 deletions
diff --git a/src/atom/mod.rs b/src/atom/mod.rs
index eb7537b..0dea415 100644
--- a/src/atom/mod.rs
+++ b/src/atom/mod.rs
@@ -2,6 +2,7 @@ use core::{
fmt::{self},
option::Option,
};
+use std::cmp::Ordering;
use crate::useflag::UseFlag;
@@ -36,7 +37,10 @@ pub struct Name(#[get(method = "get", kind = "deref")] String);
#[derive(Clone, Debug, Get)]
pub struct VersionNumber(#[get(method = "get", kind = "deref")] String);
-#[derive(Clone, Copy, Debug)]
+#[derive(Debug, Clone, Get)]
+struct VersionNumbers(#[get(method = "get", kind = "deref")] Vec<VersionNumber>);
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum VersionSuffixKind {
Alpha,
Beta,
@@ -51,13 +55,14 @@ pub struct VersionSuffix {
number: Option<VersionNumber>,
}
+#[derive(Debug, Clone, Get)]
+pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec<VersionSuffix>);
+
#[derive(Clone, Debug, Get)]
pub struct Version {
- #[get(kind = "deref")]
- numbers: Vec<VersionNumber>,
+ numbers: VersionNumbers,
letter: Option<char>,
- #[get(kind = "deref")]
- suffixes: Vec<VersionSuffix>,
+ suffixes: VersionSuffixes,
rev: Option<VersionNumber>,
}
@@ -103,7 +108,7 @@ pub struct UseDep {
condition: Option<UseDepCondition>,
}
-#[derive(Clone, Debug, Get)]
+#[derive(Clone, Debug, Get, PartialEq, Eq)]
pub struct Atom {
blocker: Option<Blocker>,
category: Category,
@@ -123,6 +128,239 @@ impl Atom {
}
}
+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<std::cmp::Ordering> {
+ 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::<u64>()
+ .unwrap()
+ .cmp(&b.0.parse::<u64>().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<Ordering> {
+ 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::<u64>().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::<u64>().unwrap() != b.get().parse::<u64>().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<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for VersionNumbers {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match self
+ .get()
+ .first()
+ .unwrap()
+ .get()
+ .parse::<u64>()
+ .unwrap()
+ .cmp(&other.get().first().unwrap().get().parse::<u64>().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::<u64>()
+ .unwrap()
+ .cmp(&b.get().parse::<u64>().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::<u64>().unwrap() == b.get().parse::<u64>().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<Ordering> {
+ 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::<u64>()
+ .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 fmt::Display for Blocker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -191,6 +429,7 @@ 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(".")
@@ -198,6 +437,7 @@ impl fmt::Display for Version {
let suffixes = self
.suffixes
+ .get()
.iter()
.map(|s| s.to_string())
.intersperse("_".to_string())
@@ -346,6 +586,22 @@ mod test {
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)
+ }
+ };
+ }
+
#[test]
fn test_version_display() {
let s = "1.0.0_alpha1_beta1-r1";
@@ -361,4 +617,57 @@ mod test {
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);
+ }
+ }
}
diff --git a/src/atom/parsers.rs b/src/atom/parsers.rs
index fd212cd..0f95d16 100644
--- a/src/atom/parsers.rs
+++ b/src/atom/parsers.rs
@@ -6,8 +6,8 @@ use crate::{
Parseable,
atom::{
Atom, Blocker, Category, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition,
- UseDepNegate, UseDepSign, Version, VersionNumber, VersionOperator, VersionSuffix,
- VersionSuffixKind,
+ UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers, VersionOperator,
+ VersionSuffix, VersionSuffixKind, VersionSuffixes,
},
useflag::UseFlag,
};
@@ -67,22 +67,45 @@ impl<'a> Parseable<'a, &'a str> for VersionSuffix {
}
}
+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("_"))
+ .many()
+ .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 numbers = VersionNumber::parser().separated_by(tag(".")).at_least(1);
- let suffixes = VersionSuffix::parser().separated_by(tag("_")).many();
let rev = VersionNumber::parser().preceded_by(tag("-r"));
- numbers
+ VersionNumbers::parser()
.and(r#if(|c: &char| c.is_ascii_alphabetic() && c.is_ascii_lowercase()).opt())
- .and(suffixes.preceded_by(tag("_")).opt())
+ .and(VersionSuffixes::parser().preceded_by(tag("_")).opt())
.and(rev.opt())
.map(|(((numbers, letter), suffixes), rev)| Version {
numbers,
letter,
- suffixes: suffixes.unwrap_or(Vec::new()),
+ suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())),
rev,
})
}
@@ -298,6 +321,7 @@ impl<'a> Parseable<'a, &'a str> for Atom {
Some((_, version))
if !version
.numbers()
+ .get()
.iter()
.any(|number| number.get().contains("*")) =>
{