use std::{cmp, fmt};
pub use error::Error;
use error::Result;
mod error;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Version {
pub numeric_identifiers: NumericIdentifiers,
pub suffix: Option<String>,
}
impl Version {
pub fn parse(text: &str) -> Result<Self> {
let vec: Vec<&str> = text.splitn(2, '-').collect();
if vec == [""] {
return Err(Error::NoVersion);
}
let numeric_identifiers = NumericIdentifiers::parse(vec.first().ok_or(Error::NoVersion)?)?;
let suffix = vec.get(1).map(|pr| pr.to_string());
Ok(Self {
numeric_identifiers,
suffix,
})
}
pub fn matches(&self, vr: &VersionRequirement) -> bool {
vr.comparators.iter().all(|c| self.matches_comparators(c))
}
fn matches_comparators(&self, cmp: &Comparator) -> bool {
let useful_len = cmp.version.numeric_identifiers.0.len();
let useful_lhs = &self.numeric_identifiers.0[..useful_len];
let useful_rhs = cmp.version.numeric_identifiers.0.as_slice();
match cmp.operator.unwrap_or(Operator::Exact) {
Operator::Exact => self == &cmp.version,
Operator::Different => self != &cmp.version,
Operator::Greater => useful_lhs > useful_rhs,
Operator::GreaterEq => useful_lhs >= useful_rhs,
Operator::Less => useful_lhs < useful_rhs,
Operator::LessEq => useful_lhs <= useful_rhs,
Operator::RightMost => {
let prefix_len = useful_rhs.len() - 1;
let prefix_lhs = &useful_lhs[..prefix_len];
let prefix_rhs = &useful_rhs[..prefix_len];
(useful_lhs >= useful_rhs) && (prefix_lhs == prefix_rhs)
}
}
}
}
impl cmp::PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.numeric_identifiers
.partial_cmp(&other.numeric_identifiers)
}
}
impl cmp::Ord for Version {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.numeric_identifiers.cmp(&other.numeric_identifiers)
}
}
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let pr = match &self.suffix {
None => "".to_string(),
Some(suffix) => format!("-{}", suffix),
};
write!(f, "{}{pr}", self.numeric_identifiers)
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct VersionRequirement {
pub comparators: Vec<Comparator>,
}
impl VersionRequirement {
pub fn parse(text: &str) -> Result<Self> {
let vec: Vec<&str> = text.split(',').map(|s| s.trim()).collect();
if vec == [""] {
return Err(Error::NoVersionRequirement);
}
let comparators: Vec<Comparator> = vec
.iter()
.map(|comp| Comparator::parse(comp))
.collect::<Result<Vec<Comparator>>>()?;
if comparators.len() > 1
&& comparators
.iter()
.any(|c| c.operator == Some(Operator::Exact) || c.operator.is_none())
{
return Err(Error::NotAllowedOperatorWithMultipleComparators(
text.to_string(),
));
}
Ok(Self { comparators })
}
pub fn is_without_operator(&self) -> bool {
match &self.comparators[..] {
[item] => item.operator.is_none(),
_ => false,
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Comparator {
pub operator: Option<Operator>,
pub version: Version,
}
impl Comparator {
fn parse(text: &str) -> Result<Self> {
let Some((operator, version)) = Comparator::split_and_parse_operator(text) else {
return Err(Error::InvalidOperator(text.to_string()));
};
let version = Version::parse(version)?;
match operator {
Some(op)
if version.suffix.is_some()
&& op != Operator::Exact
&& op != Operator::Different =>
{
return Err(Error::NotAllowedOperatorWithSuffix(op));
}
_ => {}
}
Ok(Self { operator, version })
}
#[allow(clippy::manual_map)]
fn split_and_parse_operator(text: &str) -> Option<(Option<Operator>, &str)> {
if let Some(rest) = text.strip_prefix("<=") {
Some((Some(Operator::LessEq), rest.trim_start()))
} else if let Some(rest) = text.strip_prefix(">=") {
Some((Some(Operator::GreaterEq), rest.trim_start()))
} else if let Some(rest) = text.strip_prefix("!=") {
Some((Some(Operator::Different), rest.trim_start()))
} else if let Some(rest) = text.strip_prefix("~>") {
Some((Some(Operator::RightMost), rest.trim_start()))
} else if let Some(rest) = text.strip_prefix('<') {
Some((Some(Operator::Less), rest.trim_start()))
} else if let Some(rest) = text.strip_prefix('>') {
Some((Some(Operator::Greater), rest.trim_start()))
} else if let Some(rest) = text.strip_prefix('=') {
Some((Some(Operator::Exact), rest.trim_start()))
} else if let Some(first) = text.trim_start().chars().next() {
if first.is_ascii_digit() {
Some((None, text.trim_start()))
} else {
None
}
} else {
None
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct NumericIdentifiers(Vec<u32>);
impl NumericIdentifiers {
pub fn new(vec: Vec<u32>) -> NumericIdentifiers {
NumericIdentifiers(vec)
}
pub fn parse(text: &str) -> Result<Self> {
let nums = text
.split('.')
.map(|ni| {
ni.parse::<u32>()
.map_err(|err| Error::ImpossibleNumericIdentifierParsing {
err,
text: text.into(),
ni: ni.into(),
})
})
.collect::<Result<Vec<_>>>()?;
Ok(Self(nums))
}
}
impl fmt::Display for NumericIdentifiers {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0
.iter()
.map(|integer| integer.to_string())
.collect::<Vec<String>>()
.join(".")
.fmt(f)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Operator {
Exact,
Different,
Greater,
GreaterEq,
Less,
LessEq,
RightMost,
}
impl fmt::Display for Operator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(self, f)
}
}