Skip to content

Commit

Permalink
Use pubgrub's range type
Browse files Browse the repository at this point in the history
This may be a dead end as it doesn't have enough flexibility when it
comes to prereleases

See this related issue: pubgrub-rs/pubgrub#80
  • Loading branch information
lpil committed Apr 30, 2021
1 parent 5c668c7 commit 6b6e66f
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 367 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ flate2 = "1.0"
ring = "0.16"
# PEM -> DER conversion
x509-parser = "0.9"
# Pubgrub dependency resolution algorithm
pubgrub = "0.2"

[dev-dependencies]
# HTTP mock server
Expand Down
76 changes: 66 additions & 10 deletions src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ use self::parser::Parser;

mod lexer;
mod parser;
mod requirement;
#[cfg(test)]
mod tests;

pub use requirement::*;
pub use pubgrub::range::Range;

/// In a nutshell, a version is represented by three numbers:
///
Expand All @@ -32,19 +31,19 @@ pub use requirement::*;
///
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Version {
major: u64,
minor: u64,
patch: u64,
major: u32,
minor: u32,
patch: u32,
pre: Vec<Identifier>,
build: Option<String>,
}

impl Version {
fn bump_minor(&self) -> Self {
pub fn new(major: u32, minor: u32, patch: u32) -> Self {
Self {
major: self.major,
minor: self.minor + 1,
patch: 0,
major,
minor,
patch,
pre: vec![],
build: None,
}
Expand All @@ -60,6 +59,27 @@ impl Version {
}
}

fn bump_minor(&self) -> Self {
Self {
major: self.major,
minor: self.minor + 1,
patch: 0,
pre: vec![],
build: None,
}
}

fn bump_patch(&self) -> Self {
Self {
major: self.major,
minor: self.minor,
patch: self.patch + 1,
pre: vec![],
build: None,
}
}

/// Parse a version.
pub fn parse(input: &str) -> Result<Self, parser::Error> {
let mut parser = Parser::new(input)?;
let version = parser.version()?;
Expand All @@ -68,6 +88,42 @@ impl Version {
}
Ok(version)
}

/// Parse a Hex compatible version range. i.e. `> 1 and < 2 or == 4.5.2`.
pub fn parse_range(input: &str) -> Result<Range<Self>, parser::Error> {
let mut parser = Parser::new(input)?;
let version = parser.range()?;
if !parser.is_eof() {
return Err(parser::Error::MoreInput(parser.tail()?));
}
Ok(version)
}

fn tuple(&self) -> (u32, u32, u32) {
(self.major, self.minor, self.patch)
}
}

impl std::cmp::PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.tuple().cmp(&other.tuple()))
}
}

impl std::cmp::Ord for Version {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.tuple().cmp(&other.tuple())
}
}

impl pubgrub::version::Version for Version {
fn lowest() -> Self {
Self::new(0, 0, 0)
}

fn bump(&self) -> Self {
self.bump_patch()
}
}

impl<'a> TryFrom<&'a str> for Version {
Expand Down Expand Up @@ -99,7 +155,7 @@ impl fmt::Display for Version {

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Identifier {
Numeric(u64),
Numeric(u32),
AlphaNumeric(String),
}

Expand Down
4 changes: 2 additions & 2 deletions src/version/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub enum Token<'input> {
/// any number of whitespace (`\t\r\n `) and its span.
Whitespace(usize, usize),
/// Numeric component, like `0` or `42`.
Numeric(u64),
Numeric(u32),
/// Alphanumeric component, like `alpha1` or `79deadbe`.
AlphaNumeric(&'input str),
/// An alphanumeric component with a leading zero, like `0alpha1` or `079deadbe`.
Expand Down Expand Up @@ -150,7 +150,7 @@ impl<'input> Lexer<'input> {
return Ok(Numeric(0));
}

if let Ok(numeric) = input.parse::<u64>() {
if let Ok(numeric) = input.parse::<u32>() {
// Only parse as a number if there is no leading zero
if a != Some('0') {
return Ok(Numeric(numeric));
Expand Down
142 changes: 63 additions & 79 deletions src/version/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
use std::fmt;
use std::mem;

use pubgrub::range::Range;

use self::Error::*;
use super::{
lexer::{self, Lexer, Token},
requirement::Requirement,
Comparator,
};
use crate::version::{Identifier, Operator, Version};
use super::lexer::{self, Lexer, Token};
use crate::version::{Identifier, Version};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Error<'input> {
Expand Down Expand Up @@ -111,31 +109,14 @@ impl<'input> Parser<'input> {
}
}

// /// Parse a single component.
// pub fn component(&mut self) -> Result<u64, Error<'input>> {
// match self.pop()? {
// Token::Numeric(number) => Ok(number),
// tok => Err(UnexpectedToken(tok)),
// }
// }

/// Parse a single numeric.
pub fn numeric(&mut self) -> Result<u64, Error<'input>> {
pub fn numeric(&mut self) -> Result<u32, Error<'input>> {
match self.pop()? {
Token::Numeric(number) => Ok(number),
tok => Err(UnexpectedToken(tok)),
}
}

// /// Parse a dot, then a component.
// pub fn dot_component(&mut self) -> Result<u64, Error<'input>> {
// match self.pop()? {
// Token::Dot => {}
// tok => return Err(UnexpectedToken(tok)),
// }
// self.component()
// }

fn dot(&mut self) -> Result<(), Error<'input>> {
match self.pop()? {
Token::Dot => Ok(()),
Expand All @@ -144,7 +125,7 @@ impl<'input> Parser<'input> {
}

/// Parse a dot, then a numeric.
fn dot_numeric(&mut self) -> Result<u64, Error<'input>> {
fn dot_numeric(&mut self) -> Result<u32, Error<'input>> {
self.dot()?;
self.numeric()
}
Expand Down Expand Up @@ -256,20 +237,18 @@ impl<'input> Parser<'input> {
})
}

/// Parse a requirement.
/// Parse a version range requirement.
///
/// Like, `~> 1.0.0` or `3.0.0-beta.1 or < 1.0 and > 0.2.3`.
pub fn requirement(&mut self) -> Result<Requirement, Error<'input>> {
let alternatives = self.requirement_alternatives()?;
self.skip_whitespace()?;
Ok(Requirement(alternatives))
}

fn requirement_alternatives(&mut self) -> Result<Vec<Vec<Comparator>>, Error<'input>> {
let mut alternatives = Vec::new();
pub fn range(&mut self) -> Result<Range<Version>, Error<'input>> {
let mut range: Option<Range<Version>> = None;

loop {
alternatives.push(self.requirement_comparators()?);
let constraint = self.range_ands_section()?;
range = Some(match range {
None => constraint,
Some(range) => range.union(&constraint),
});
if self.peek() == Some(&Token::Or) {
self.pop()?;
self.expect_whitespace()?;
Expand All @@ -278,29 +257,11 @@ impl<'input> Parser<'input> {
}
}

if alternatives.is_empty() {
Err(UnexpectedEnd)
} else {
Ok(alternatives)
}
}

fn basic_comparison_operator(&mut self) -> Result<Operator, Error<'input>> {
match self.pop()? {
Token::Eq => Ok(Operator::Eq),
Token::NotEq => Ok(Operator::NotEq),
Token::Gt => Ok(Operator::Gt),
Token::Lt => Ok(Operator::Lt),
Token::LtEq => Ok(Operator::LtEq),
Token::GtEq => Ok(Operator::GtEq),

token => Err(UnexpectedToken(token)),
}
self.skip_whitespace()?;
range.ok_or(UnexpectedEnd)
}

fn pessimistic_version_constraint(
&mut self,
) -> Result<(Comparator, Comparator), Error<'input>> {
fn pessimistic_version_constraint(&mut self) -> Result<Range<Version>, Error<'input>> {
let mut included_patch = false;
let major = self.numeric()?;
let minor = self.dot_numeric()?;
Expand All @@ -327,38 +288,65 @@ impl<'input> Parser<'input> {
} else {
lower.bump_major()
};
let lower = Comparator {
operator: Operator::GtEq,
version: lower,
};
let upper = Comparator {
operator: Operator::Lt,
version: upper,
};
Ok((lower, upper))
Ok(Range::higher_than(lower).intersection(&Range::strictly_lower_than(upper)))
}

fn requirement_comparators(&mut self) -> Result<Vec<Comparator>, Error<'input>> {
fn range_ands_section(&mut self) -> Result<Range<Version>, Error<'input>> {
use Token::*;
let mut comparators = Vec::new();
let comp = |operator, version| Comparator { operator, version };
let mut range = None;
let and = |range: Option<Range<Version>>, constraint: Range<Version>| {
Some(match range {
None => constraint,
Some(range) => range.intersection(&constraint),
})
};
loop {
self.skip_whitespace()?;
match self.peek() {
None => break,
Some(Numeric(_)) => range = and(range, Range::exact(self.version()?)),

Some(Numeric(_)) => comparators.push(comp(Operator::Eq, self.version()?)),
Some(Eq) => {
self.pop()?;
range = and(range, Range::exact(self.version()?));
}

Some(NotEq) => {
self.pop()?;
let version = self.version()?;
let bumped = version.bump_patch();
let below = Range::strictly_lower_than(version);
let above = Range::higher_than(bumped);
range = and(range, below.union(&above));
}

Some(Gt) => {
self.pop()?;
range = and(range, Range::higher_than(self.version()?.bump_patch()));
}

Some(GtEq) => {
self.pop()?;
range = and(range, Range::higher_than(self.version()?));
}

Some(Lt) | Some(LtEq) | Some(GtEq) | Some(Eq) | Some(Gt) | Some(NotEq) => {
comparators.push(comp(self.basic_comparison_operator()?, self.version()?))
Some(Lt) => {
self.pop()?;
range = and(range, Range::strictly_lower_than(self.version()?));
}

Some(LtEq) => {
self.pop()?;
range = and(
range,
Range::strictly_lower_than(self.version()?.bump_patch()),
);
}

Some(Pessimistic) => {
self.pop()?;
self.skip_whitespace()?;
let (lower, upper) = self.pessimistic_version_constraint()?;
comparators.push(lower);
comparators.push(upper);
range = and(range, self.pessimistic_version_constraint()?);
}

Some(_) => return Err(UnexpectedToken(self.pop()?)),
Expand All @@ -370,11 +358,7 @@ impl<'input> Parser<'input> {
break;
}
}
if comparators.is_empty() {
Err(UnexpectedEnd)
} else {
Ok(comparators)
}
range.ok_or(UnexpectedEnd)
}

/// Check if we have reached the end of input.
Expand Down
Loading

0 comments on commit 6b6e66f

Please sign in to comment.