forked from gentoo-utils/gentoo-utils
impl version comparison algorithm
This commit is contained in:
321
src/atom/mod.rs
321
src/atom/mod.rs
@@ -2,6 +2,7 @@ use core::{
|
|||||||
fmt::{self},
|
fmt::{self},
|
||||||
option::Option,
|
option::Option,
|
||||||
};
|
};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use crate::useflag::UseFlag;
|
use crate::useflag::UseFlag;
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ pub struct Name(#[get(method = "get", kind = "deref")] String);
|
|||||||
#[derive(Clone, Debug, Get)]
|
#[derive(Clone, Debug, Get)]
|
||||||
pub struct VersionNumber(#[get(method = "get", kind = "deref")] String);
|
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 {
|
pub enum VersionSuffixKind {
|
||||||
Alpha,
|
Alpha,
|
||||||
Beta,
|
Beta,
|
||||||
@@ -51,13 +55,14 @@ pub struct VersionSuffix {
|
|||||||
number: Option<VersionNumber>,
|
number: Option<VersionNumber>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Get)]
|
||||||
|
pub struct VersionSuffixes(#[get(method = "get", kind = "deref")] Vec<VersionSuffix>);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Get)]
|
#[derive(Clone, Debug, Get)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
#[get(kind = "deref")]
|
numbers: VersionNumbers,
|
||||||
numbers: Vec<VersionNumber>,
|
|
||||||
letter: Option<char>,
|
letter: Option<char>,
|
||||||
#[get(kind = "deref")]
|
suffixes: VersionSuffixes,
|
||||||
suffixes: Vec<VersionSuffix>,
|
|
||||||
rev: Option<VersionNumber>,
|
rev: Option<VersionNumber>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +108,7 @@ pub struct UseDep {
|
|||||||
condition: Option<UseDepCondition>,
|
condition: Option<UseDepCondition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Get)]
|
#[derive(Clone, Debug, Get, PartialEq, Eq)]
|
||||||
pub struct Atom {
|
pub struct Atom {
|
||||||
blocker: Option<Blocker>,
|
blocker: Option<Blocker>,
|
||||||
category: Category,
|
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 {
|
impl fmt::Display for Blocker {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -191,6 +429,7 @@ impl fmt::Display for Version {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let numbers = self
|
let numbers = self
|
||||||
.numbers
|
.numbers
|
||||||
|
.get()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|n| n.get())
|
.map(|n| n.get())
|
||||||
.intersperse(".")
|
.intersperse(".")
|
||||||
@@ -198,6 +437,7 @@ impl fmt::Display for Version {
|
|||||||
|
|
||||||
let suffixes = self
|
let suffixes = self
|
||||||
.suffixes
|
.suffixes
|
||||||
|
.get()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.intersperse("_".to_string())
|
.intersperse("_".to_string())
|
||||||
@@ -346,6 +586,22 @@ mod test {
|
|||||||
|
|
||||||
use crate::Parseable;
|
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]
|
#[test]
|
||||||
fn test_version_display() {
|
fn test_version_display() {
|
||||||
let s = "1.0.0_alpha1_beta1-r1";
|
let s = "1.0.0_alpha1_beta1-r1";
|
||||||
@@ -361,4 +617,57 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(atom.to_string().as_str(), s);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use crate::{
|
|||||||
Parseable,
|
Parseable,
|
||||||
atom::{
|
atom::{
|
||||||
Atom, Blocker, Category, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition,
|
Atom, Blocker, Category, Name, Slot, SlotName, SlotOperator, UseDep, UseDepCondition,
|
||||||
UseDepNegate, UseDepSign, Version, VersionNumber, VersionOperator, VersionSuffix,
|
UseDepNegate, UseDepSign, Version, VersionNumber, VersionNumbers, VersionOperator,
|
||||||
VersionSuffixKind,
|
VersionSuffix, VersionSuffixKind, VersionSuffixes,
|
||||||
},
|
},
|
||||||
useflag::UseFlag,
|
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 {
|
impl<'a> Parseable<'a, &'a str> for Version {
|
||||||
type Parser = impl Parser<&'a str, Output = Self>;
|
type Parser = impl Parser<&'a str, Output = Self>;
|
||||||
|
|
||||||
fn parser() -> Self::Parser {
|
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"));
|
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(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())
|
.and(rev.opt())
|
||||||
.map(|(((numbers, letter), suffixes), rev)| Version {
|
.map(|(((numbers, letter), suffixes), rev)| Version {
|
||||||
numbers,
|
numbers,
|
||||||
letter,
|
letter,
|
||||||
suffixes: suffixes.unwrap_or(Vec::new()),
|
suffixes: suffixes.unwrap_or(VersionSuffixes(Vec::new())),
|
||||||
rev,
|
rev,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -298,6 +321,7 @@ impl<'a> Parseable<'a, &'a str> for Atom {
|
|||||||
Some((_, version))
|
Some((_, version))
|
||||||
if !version
|
if !version
|
||||||
.numbers()
|
.numbers()
|
||||||
|
.get()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|number| number.get().contains("*")) =>
|
.any(|number| number.get().contains("*")) =>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user