use crate::Error;
use std::{cmp::Ordering, fmt::Display, str::FromStr};
#[derive(Debug, Clone)]
pub struct DebVersion {
pub(crate) epoch: u32,
pub(crate) version: String,
pub(crate) revision: Option<String>,
}
impl DebVersion {
pub fn epoch(&self) -> u32 {
self.epoch
}
pub fn version(&self) -> &str {
&self.version
}
pub fn revision(&self) -> Option<&str> {
self.revision.as_deref()
}
}
impl PartialEq for DebVersion {
fn eq(&self, other: &Self) -> bool {
dpkg_version_compare(self, other) == Ordering::Equal
}
}
impl Eq for DebVersion {}
impl PartialOrd for DebVersion {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DebVersion {
fn cmp(&self, other: &Self) -> Ordering {
dpkg_version_compare(self, other)
}
}
impl FromStr for DebVersion {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
crate::parsehelp::parseversion(input)
}
}
impl Display for DebVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.epoch > 0 {
write!(f, "{}:", self.epoch)?;
}
write!(f, "{}", self.version)?;
if let Some(revision) = &self.revision {
write!(f, "-{revision}")?;
}
Ok(())
}
}
fn order(char: char) -> i32 {
match char {
'0'..='9' => 0,
'A'..='Z' | 'a'..='z' => char as i32,
'~' => -1,
val => val as i32 + 256,
}
}
fn verrevcmp(a: &str, b: &str) -> i32 {
let mut index_a = 0;
let mut index_b = 0;
let chars_a = a.chars().collect::<Vec<_>>();
let chars_b = b.chars().collect::<Vec<_>>();
while index_a < a.len() || index_b < b.len() {
let mut first_diff = 0;
while (index_a < a.len() && !chars_a[index_a].is_ascii_digit())
|| (index_b < b.len() && !chars_b[index_b].is_ascii_digit())
{
let ac = if index_a < a.len() {
order(chars_a[index_a])
} else {
0
};
let bc = if index_b < b.len() {
order(chars_b[index_b])
} else {
0
};
if ac != bc {
return ac - bc;
}
index_a += 1;
index_b += 1;
}
while index_a < a.len() && chars_a[index_a] == '0' {
index_a += 1;
}
while index_b < b.len() && chars_b[index_b] == '0' {
index_b += 1;
}
while index_a < a.len()
&& chars_a[index_a].is_ascii_digit()
&& index_b < b.len()
&& chars_b[index_b].is_ascii_digit()
{
if first_diff == 0 {
first_diff = chars_a[index_a] as i32 - chars_b[index_b] as i32;
}
index_a += 1;
index_b += 1;
}
if index_a < a.len() && chars_a[index_a].is_ascii_digit() {
return 1;
}
if index_b < b.len() && chars_b[index_b].is_ascii_digit() {
return -1;
}
if first_diff != 0 {
return first_diff;
}
}
0
}
pub(crate) fn dpkg_version_compare(a: &DebVersion, b: &DebVersion) -> Ordering {
if a.epoch > b.epoch {
return Ordering::Greater;
}
if a.epoch < b.epoch {
return Ordering::Less;
}
let rc = verrevcmp(&a.version, &b.version);
if rc != 0 {
return rc.cmp(&0);
}
match (&a.revision, &b.revision) {
(Some(a_revision), Some(b_revision)) => verrevcmp(a_revision, b_revision).cmp(&0),
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Less,
(Some(_), None) => Ordering::Greater,
}
}