Skip to content

Commit

Permalink
Refactor about tilde and exactly Comparator Op
Browse files Browse the repository at this point in the history
  • Loading branch information
linyihai committed Jul 18, 2024
1 parent 074c7c3 commit 34bec6c
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 55 deletions.
177 changes: 147 additions & 30 deletions src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use crate::{Comparator, Op, Prerelease, Version, VersionReq};

pub(crate) fn matches_req(req: &VersionReq, ver: &Version, prerelease_matches: bool) -> bool {
for cmp in &req.comparators {
if !matches_impl(cmp, ver, prerelease_matches) {
if prerelease_matches {
if !matches_prerelease_impl(cmp, ver) {
return false;
}
} else if !matches_impl(cmp, ver) {
return false;
}
}
Expand All @@ -24,28 +28,94 @@ pub(crate) fn matches_req(req: &VersionReq, ver: &Version, prerelease_matches: b
}

pub(crate) fn matches_comparator(cmp: &Comparator, ver: &Version) -> bool {
matches_impl(cmp, ver, false) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver))
matches_impl(cmp, ver) && (ver.pre.is_empty() || pre_is_compatible(cmp, ver))
}
// If VersionReq missing Minor, Patch, then filling them with 0
fn fill_partial_req(cmp: &mut Comparator) {
if cmp.minor.is_none() {
cmp.minor = Some(0);
cmp.patch = Some(0);
} else if cmp.patch.is_none() {
cmp.patch = Some(0);
}
}

fn matches_prerelease_impl(cmp: &Comparator, ver: &Version) -> bool {
let cmp = &{
let mut cmp = cmp.clone();
if matches!(cmp.op, Op::Greater | Op::GreaterEq | Op::Less | Op::LessEq) {
fill_partial_req(&mut cmp)
}
cmp
};
match cmp.op {
Op::Exact | Op::Wildcard => matches_exact_prerelease(cmp, ver),
Op::Greater => matches_greater(cmp, ver),
Op::GreaterEq => matches_exact(cmp, ver) || matches_greater(cmp, ver),
Op::Less => matches_less(cmp, ver),
Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver),
Op::Tilde => matches_tilde_prerelease(cmp, ver),
Op::Caret => matches_caret_prerelease(cmp, ver),
#[cfg(no_non_exhaustive)]
Op::__NonExhaustive => unreachable!(),
}
}

fn matches_impl(cmp: &Comparator, ver: &Version, prerelease_matches: bool) -> bool {
fn matches_impl(cmp: &Comparator, ver: &Version) -> bool {
match cmp.op {
Op::Exact | Op::Wildcard => matches_exact(cmp, ver),
Op::Greater => matches_greater(cmp, ver),
Op::GreaterEq => matches_exact(cmp, ver) || matches_greater(cmp, ver),
Op::Less => matches_less(cmp, ver),
Op::LessEq => matches_exact(cmp, ver) || matches_less(cmp, ver),
Op::Tilde => matches_tilde(cmp, ver),
Op::Caret => {
if prerelease_matches {
return matches_caret_prerelease(cmp, ver);
}
matches_caret(cmp, ver)
}
Op::Caret => matches_caret(cmp, ver),
#[cfg(no_non_exhaustive)]
Op::__NonExhaustive => unreachable!(),
}
}

fn matches_exact_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}

let mut lower = Comparator {
op: Op::Less,
..cmp.clone()
};
fill_partial_req(&mut lower);
if !matches_greater(&lower, ver) {
return false;
}

let mut upper = Comparator {
op: Op::Less,
pre: Prerelease::new("0").unwrap(),
..cmp.clone()
};

match (upper.minor.is_some(), upper.patch.is_some()) {
(true, true) => {
upper.patch = Some(upper.patch.unwrap() + 1);
}
(true, false) => {
// Partial Exact VersionReq eg. =0.24
upper.minor = Some(upper.minor.unwrap() + 1);
upper.patch = Some(0);
}
(false, false) => {
// Partial Exact VersionReq eg. =0
upper.major += 1;
upper.minor = Some(0);
upper.patch = Some(0);
}
_ => {}
}

matches_less(&upper, ver)
}

fn matches_exact(cmp: &Comparator, ver: &Version) -> bool {
if ver.major != cmp.major {
return false;
Expand Down Expand Up @@ -118,6 +188,39 @@ fn matches_less(cmp: &Comparator, ver: &Version) -> bool {
ver.pre < cmp.pre
}

fn matches_tilde_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}

let mut lower = cmp.clone();
fill_partial_req(&mut lower);
if !matches_greater(&lower, ver) {
return false;
}

let mut upper = Comparator {
op: Op::Less,
pre: Prerelease::new("0").unwrap(),
..cmp.clone()
};

match (upper.minor.is_some(), upper.patch.is_some()) {
(true, _) => {
upper.minor = Some(upper.minor.unwrap() + 1);
upper.patch = Some(0);
}
(false, false) => {
upper.major += 1;
upper.minor = Some(0);
upper.patch = Some(0);
}
_ => {}
}

matches_less(&upper, ver)
}

fn matches_tilde(cmp: &Comparator, ver: &Version) -> bool {
if ver.major != cmp.major {
return false;
Expand All @@ -142,33 +245,47 @@ fn matches_caret_prerelease(cmp: &Comparator, ver: &Version) -> bool {
if matches_exact(cmp, ver) {
return true;
}
if !matches_greater(cmp, ver) {
let mut lower = cmp.clone();
fill_partial_req(&mut lower);
if !matches_greater(&lower, ver) {
return false;
}

let mut pre_cmp = Comparator {
op: Op::LessEq,
let mut upper = Comparator {
op: Op::Less,
pre: Prerelease::new("0").unwrap(),
..cmp.clone()
};
let Comparator {
major,
minor,
patch,
..
} = *cmp;

if major > 0 {
pre_cmp.major += 1;
pre_cmp.minor = Some(0);
pre_cmp.patch = Some(0);
} else if minor.is_some() && minor.unwrap() > 0 {
pre_cmp.minor = Some(minor.unwrap() + 1);
pre_cmp.patch = Some(0);
} else {
pre_cmp.patch = Some(patch.unwrap() + 1);
}
matches_less(&pre_cmp, ver)

match (
upper.major > 0,
upper.minor.is_some(),
upper.patch.is_some(),
) {
(true, _, _) | (_, false, false) => {
upper.major += 1;
upper.minor = Some(0);
upper.patch = Some(0);
}
(_, true, false) => {
upper.minor = Some(upper.minor.unwrap() + 1);
upper.patch = Some(0);
}
(_, true, _) if upper.minor.unwrap() > 0 => {
upper.minor = Some(upper.minor.unwrap() + 1);
upper.patch = Some(0);
}
(_, true, _) if upper.minor.unwrap() == 0 => {
if upper.patch.is_none() {
upper.patch = Some(1);
} else {
upper.patch = Some(upper.patch.unwrap() + 1);
}
}
_ => {}
}

matches_less(&upper, ver)
}

fn matches_caret(cmp: &Comparator, ver: &Version) -> bool {
Expand Down
22 changes: 1 addition & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,27 +526,7 @@ impl VersionReq {

/// Simliar to [`Self::matches`], it allows to match any "Semver-compatible" pre-release version.
pub fn matches_prerelease(&self, version: &Version) -> bool {
let req = {
let mut req = self.clone();
req.comparators = req
.comparators
.into_iter()
.map(|mut cmp| {
// Paritial VersionReq (eg. `0.24`) needs to be filled with minor and patch,
// so that we can make sure `0.24.2-pre` matches `~0.24` or `0.24`
if cmp.minor.is_none() {
cmp.minor = Some(0);
cmp.patch = Some(0);
} else if cmp.patch.is_none() {
cmp.patch = Some(0);
}
cmp
})
.collect();
req
};

eval::matches_req(&req, version, true)
eval::matches_req(self, version, true)
}
}

Expand Down
76 changes: 72 additions & 4 deletions tests/test_matches_prerelease.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ fn minor_caret() {

#[test]
fn major_caret() {
let ref r = req("=0.0.0-r");
assert_prerelease_match_all(r, &["0.0.0"]);

let ref r = req("0");
// Match >= 0.0.0, < 1.0.0-0
assert_prerelease_match_all(r, &["0.0.0", "0.0.1-0", "0.0.1-pre", "0.1.1"]);
// Not Match < 0.0.0
assert_prerelease_match_none(r, &["0.0.0-pre"]);
// Not Match >= 1.0.0-0
assert_prerelease_match_none(r, &["1.0.0-0", "1.0.0", "1.0.1-pre", "2.0.0-0"]);

let ref r = req("0.0");
// Match >= 0.0.0, < 0.1.0-0
assert_prerelease_match_all(r, &["0.0.1-z0", "0.0.9"]);
// Not Match >= 0.1.0-0
assert_prerelease_match_none(r, &["0.1.0-0", "0.1.0", "0.1.1-pre", "0.1.1-z0", "1.1.0"]);

let ref r = req("0.0.0");
// Match >= 0.0.0, < 0.0.1-0
assert_prerelease_match_all(r, &["0.0.0"]);
// Not Match >= 0.0.1-0
assert_prerelease_match_none(r, &["0.0.1-0", "0.0.1", "1.0.1-pre"]);

let ref r = req("1");
// Match >= 1.0.0, < 2.0.0-0
assert_prerelease_match_all(r, &["1.2.3", "1.2.4-0", "1.2.4", "1.8.8"]);
Expand Down Expand Up @@ -125,12 +148,12 @@ fn test_tilde() {
assert_prerelease_match_none(r, &["0.25.0-0", "1.1.0", "1.2.3", "2.0.0"]);

let ref r = req("~1");
// Match >= 1.0.0, < 1.1.0-0
assert_prerelease_match_all(r, &["1.0.0", "1.0.1-pre", "1.0.1", "1.0.9"]);
// Match >= 1.0.0, < 2.0.0-0
assert_prerelease_match_all(r, &["1.0.0", "1.1.0-0", "1.1.0", "1.2.3"]);
// Not Match < 1.0.0
assert_prerelease_match_none(r, &["0.0.1", "0.9.9", "1.0.0-pre"]);
// Not Match >= 1.1.0-0
assert_prerelease_match_none(r, &["1.1.0-0", "1.1.0", "1.2.3", "2.0.0"]);
// Not Match >= 2.0.0-0
assert_prerelease_match_none(r, &["2.0.0-0", "2.0.0", "2.0.1"]);

let ref r = req("~1.2");
// Match >= 1.2.0, < 1.3.0-0
Expand Down Expand Up @@ -216,4 +239,49 @@ fn test_range_partial() {

let ref r = req("*");
assert_prerelease_match_all(r, &["0.0.1", "1.0.0", "1.2.9", "2.0.0-pre"]);

let ref r = req("^1, <=1.9");
assert_prerelease_match_all(r, &["1.1.1-pre", "1.1.1"]);
let ref r = req("^0, <=0.0.1-z0");
assert_prerelease_match_all(r, &["0.0.1-z0"]);
}

#[test]
fn test_exact() {
let ref r = req("=4");
// Match >= 4.0.0, < 5.0.0-0
assert_prerelease_match_all(r, &["4.0.0", "4.2.1", "4.2.4-pre", "4.9.9"]);
// Not Match < 4.0.0
assert_prerelease_match_none(r, &["0.0.1", "2.1.2-pre", "4.0.0-pre"]);
// Not Match >= 5.0.0-0
assert_prerelease_match_none(r, &["5.0.0-0", "5.0.0", "5.0.1"]);

let ref r = req("=4.2");
// Match >= 4.2.0, < 4.3.0-0
assert_prerelease_match_all(r, &["4.2.0", "4.2.1", "4.2.4-pre", "4.2.9"]);
// Not Match < 4.2.0
assert_prerelease_match_none(r, &["0.0.1", "2.1.2-pre", "4.0.0-pre"]);
// Not Match >= 4.3.0-0
assert_prerelease_match_none(r, &["4.3.0-0", "4.3.0", "5.0.0-0", "5.0.0", "5.0.1"]);

let ref r = req("=4.2.1");
assert_prerelease_match_all(r, &["4.2.1"]);
assert_prerelease_match_none(r, &["1.2.3", "4.2.1-pre", "4.2.2", "5.0.0"]);

let ref r = req("=4.2.1-0");
// Match >= 4.2.1-0 < 4.2.2-0
assert_prerelease_match_all(r, &["4.2.1-0", "4.2.1-1", "4.2.1-pre"]);
// Not Match < 4.2.1-0
assert_prerelease_match_none(r, &["1.2.3", "4.2.0"]);
// Not Match >= 4.2.2-0
assert_prerelease_match_none(r, &["4.2.2-0", "4.2.2", "4.3.5", "6.8.9"]);

// Speicial Case
let ref r = req("=0");
// Match >= 0.0.0, < 1.0.0-0
assert_prerelease_match_all(r, &["0.0.0", "0.1.1", "0.9.9"]);
// Not Match < 0.0.0
assert_prerelease_match_none(r, &["0.0.0-0", "0.0.0-pre"]);
// Not Match >= 1.0.0-0
assert_prerelease_match_none(r, &["1.0.0-0", "1.0.0", "2.0.1"]);
}

0 comments on commit 34bec6c

Please sign in to comment.