mirror of https://github.com/astral-sh/uv
Move `LowerBound` and `UpperBound` structs in `uv-pep440` (#11950)
## Summary I want to use these in `uv-python` and there's nothing specific to the resolver or even to Python in these structs.
This commit is contained in:
parent
6132d252d6
commit
8f8c0e8918
|
|
@ -24,7 +24,9 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "version-ranges")]
|
||||
pub use version_ranges::{release_specifier_to_range, release_specifiers_to_ranges};
|
||||
pub use version_ranges::{
|
||||
release_specifier_to_range, release_specifiers_to_ranges, LowerBound, UpperBound,
|
||||
};
|
||||
pub use {
|
||||
version::{
|
||||
LocalSegment, LocalVersion, LocalVersionSlice, Operator, OperatorParseError, Prerelease,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
//! Convert [`VersionSpecifiers`] to [`Ranges`].
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::Bound;
|
||||
use std::ops::Deref;
|
||||
use version_ranges::Ranges;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -209,3 +212,277 @@ pub fn release_specifier_to_range(specifier: VersionSpecifier) -> Ranges<Version
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A lower bound for a version range.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LowerBound(pub Bound<Version>);
|
||||
|
||||
impl LowerBound {
|
||||
/// Initialize a [`LowerBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`LowerBound`] truncated to the major and minor version.
|
||||
#[must_use]
|
||||
pub fn major_minor(&self) -> Self {
|
||||
match &self.0 {
|
||||
// Ex) `>=3.10.1` -> `>=3.10`
|
||||
Bound::Included(version) => Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
))),
|
||||
// Ex) `>3.10.1` -> `>=3.10`.
|
||||
Bound::Excluded(version) => Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
))),
|
||||
Bound::Unbounded => Self(Bound::Unbounded),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the lower bound contains the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
match self.0 {
|
||||
Bound::Included(ref bound) => bound <= version,
|
||||
Bound::Excluded(ref bound) => bound < version,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifier`] for the lower bound.
|
||||
pub fn specifier(&self) -> Option<VersionSpecifier> {
|
||||
match &self.0 {
|
||||
Bound::Included(version) => Some(VersionSpecifier::greater_than_equal_version(
|
||||
version.clone(),
|
||||
)),
|
||||
Bound::Excluded(version) => {
|
||||
Some(VersionSpecifier::greater_than_version(version.clone()))
|
||||
}
|
||||
Bound::Unbounded => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for LowerBound {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// See: <https://github.com/pubgrub-rs/pubgrub/blob/4b4b44481c5f93f3233221dc736dd23e67e00992/src/range.rs#L324>
|
||||
impl Ord for LowerBound {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let left = self.0.as_ref();
|
||||
let right = other.0.as_ref();
|
||||
|
||||
match (left, right) {
|
||||
// left: ∞-----
|
||||
// right: ∞-----
|
||||
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
|
||||
// left: [---
|
||||
// right: ∞-----
|
||||
(Bound::Included(_left), Bound::Unbounded) => Ordering::Greater,
|
||||
// left: ]---
|
||||
// right: ∞-----
|
||||
(Bound::Excluded(_left), Bound::Unbounded) => Ordering::Greater,
|
||||
// left: ∞-----
|
||||
// right: [---
|
||||
(Bound::Unbounded, Bound::Included(_right)) => Ordering::Less,
|
||||
// left: [----- OR [----- OR [-----
|
||||
// right: [--- OR [----- OR [---
|
||||
(Bound::Included(left), Bound::Included(right)) => left.cmp(right),
|
||||
(Bound::Excluded(left), Bound::Included(right)) => match left.cmp(right) {
|
||||
// left: ]-----
|
||||
// right: [---
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: ]-----
|
||||
// right: [---
|
||||
Ordering::Equal => Ordering::Greater,
|
||||
// left: ]---
|
||||
// right: [-----
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
// left: ∞-----
|
||||
// right: ]---
|
||||
(Bound::Unbounded, Bound::Excluded(_right)) => Ordering::Less,
|
||||
(Bound::Included(left), Bound::Excluded(right)) => match left.cmp(right) {
|
||||
// left: [-----
|
||||
// right: ]---
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: [-----
|
||||
// right: ]---
|
||||
Ordering::Equal => Ordering::Less,
|
||||
// left: [---
|
||||
// right: ]-----
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
// left: ]----- OR ]----- OR ]---
|
||||
// right: ]--- OR ]----- OR ]-----
|
||||
(Bound::Excluded(left), Bound::Excluded(right)) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LowerBound {
|
||||
fn default() -> Self {
|
||||
Self(Bound::Unbounded)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LowerBound {
|
||||
type Target = Bound<Version>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LowerBound> for Bound<Version> {
|
||||
fn from(bound: LowerBound) -> Self {
|
||||
bound.0
|
||||
}
|
||||
}
|
||||
|
||||
/// An upper bound for a version range.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct UpperBound(pub Bound<Version>);
|
||||
|
||||
impl UpperBound {
|
||||
/// Initialize a [`UpperBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`UpperBound`] truncated to the major and minor version.
|
||||
#[must_use]
|
||||
pub fn major_minor(&self) -> Self {
|
||||
match &self.0 {
|
||||
// Ex) `<=3.10.1` -> `<=3.10`
|
||||
Bound::Included(version) => Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
))),
|
||||
// Ex) `<3.10.1` -> `<=3.10` (but `<3.10.0` is `<3.10`)
|
||||
Bound::Excluded(version) => {
|
||||
if version.release().get(2).is_some_and(|patch| *patch > 0) {
|
||||
Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
)))
|
||||
} else {
|
||||
Self(Bound::Excluded(Version::new(
|
||||
version.release().iter().take(2),
|
||||
)))
|
||||
}
|
||||
}
|
||||
Bound::Unbounded => Self(Bound::Unbounded),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the upper bound contains the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
match self.0 {
|
||||
Bound::Included(ref bound) => bound >= version,
|
||||
Bound::Excluded(ref bound) => bound > version,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifier`] for the upper bound.
|
||||
pub fn specifier(&self) -> Option<VersionSpecifier> {
|
||||
match &self.0 {
|
||||
Bound::Included(version) => {
|
||||
Some(VersionSpecifier::less_than_equal_version(version.clone()))
|
||||
}
|
||||
Bound::Excluded(version) => Some(VersionSpecifier::less_than_version(version.clone())),
|
||||
Bound::Unbounded => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for UpperBound {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// See: <https://github.com/pubgrub-rs/pubgrub/blob/4b4b44481c5f93f3233221dc736dd23e67e00992/src/range.rs#L324>
|
||||
impl Ord for UpperBound {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let left = self.0.as_ref();
|
||||
let right = other.0.as_ref();
|
||||
|
||||
match (left, right) {
|
||||
// left: -----∞
|
||||
// right: -----∞
|
||||
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
|
||||
// left: ---]
|
||||
// right: -----∞
|
||||
(Bound::Included(_left), Bound::Unbounded) => Ordering::Less,
|
||||
// left: ---[
|
||||
// right: -----∞
|
||||
(Bound::Excluded(_left), Bound::Unbounded) => Ordering::Less,
|
||||
// left: -----∞
|
||||
// right: ---]
|
||||
(Bound::Unbounded, Bound::Included(_right)) => Ordering::Greater,
|
||||
// left: -----] OR -----] OR ---]
|
||||
// right: ---] OR -----] OR -----]
|
||||
(Bound::Included(left), Bound::Included(right)) => left.cmp(right),
|
||||
(Bound::Excluded(left), Bound::Included(right)) => match left.cmp(right) {
|
||||
// left: ---[
|
||||
// right: -----]
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: -----[
|
||||
// right: -----]
|
||||
Ordering::Equal => Ordering::Less,
|
||||
// left: -----[
|
||||
// right: ---]
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
(Bound::Unbounded, Bound::Excluded(_right)) => Ordering::Greater,
|
||||
(Bound::Included(left), Bound::Excluded(right)) => match left.cmp(right) {
|
||||
// left: ---]
|
||||
// right: -----[
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: -----]
|
||||
// right: -----[
|
||||
Ordering::Equal => Ordering::Greater,
|
||||
// left: -----]
|
||||
// right: ---[
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
// left: -----[ OR -----[ OR ---[
|
||||
// right: ---[ OR -----[ OR -----[
|
||||
(Bound::Excluded(left), Bound::Excluded(right)) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UpperBound {
|
||||
fn default() -> Self {
|
||||
Self(Bound::Unbounded)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for UpperBound {
|
||||
type Target = Bound<Version>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UpperBound> for Bound<Version> {
|
||||
fn from(bound: UpperBound) -> Self {
|
||||
bound.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use uv_distribution_types::{
|
|||
DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
|
||||
};
|
||||
use uv_normalize::{ExtraName, InvalidNameError, PackageName};
|
||||
use uv_pep440::{LocalVersionSlice, Version};
|
||||
use uv_pep440::{LocalVersionSlice, LowerBound, Version};
|
||||
use uv_platform_tags::Tags;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
|
|
@ -24,7 +24,6 @@ use crate::fork_urls::ForkUrls;
|
|||
use crate::prerelease::AllowPrerelease;
|
||||
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
|
||||
use crate::python_requirement::PythonRequirement;
|
||||
use crate::requires_python::LowerBound;
|
||||
use crate::resolution::ConflictingDistributionError;
|
||||
use crate::resolver::{
|
||||
MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use pubgrub::Ranges;
|
|||
use smallvec::SmallVec;
|
||||
use std::ops::Bound;
|
||||
|
||||
use uv_pep440::Version;
|
||||
use uv_pep440::{LowerBound, UpperBound, Version};
|
||||
use uv_pep508::{CanonicalMarkerValueVersion, MarkerTree, MarkerTreeKind};
|
||||
|
||||
use crate::requires_python::{LowerBound, RequiresPythonRange, UpperBound};
|
||||
use crate::requires_python::RequiresPythonRange;
|
||||
|
||||
/// Returns the bounding Python versions that can satisfy the [`MarkerTree`], if it's constrained.
|
||||
pub(crate) fn requires_python(tree: MarkerTree) -> Option<RequiresPythonRange> {
|
||||
|
|
@ -91,10 +91,10 @@ pub(crate) fn requires_python(tree: MarkerTree) -> Option<RequiresPythonRange> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::ops::Bound;
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::*;
|
||||
use uv_pep440::UpperBound;
|
||||
|
||||
#[test]
|
||||
fn test_requires_python() {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::collections::Bound;
|
||||
use std::ops::Deref;
|
||||
|
||||
use pubgrub::Range;
|
||||
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_pep440::{release_specifiers_to_ranges, Version, VersionSpecifier, VersionSpecifiers};
|
||||
use uv_pep440::{
|
||||
release_specifiers_to_ranges, LowerBound, UpperBound, Version, VersionSpecifier,
|
||||
VersionSpecifiers,
|
||||
};
|
||||
use uv_pep508::{MarkerExpression, MarkerTree, MarkerValueVersion};
|
||||
use uv_platform_tags::{AbiTag, LanguageTag};
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ impl RequiresPython {
|
|||
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
|
||||
Self {
|
||||
specifiers: specifiers.clone(),
|
||||
range: RequiresPythonRange(LowerBound(lower_bound), UpperBound(upper_bound)),
|
||||
range: RequiresPythonRange(LowerBound::new(lower_bound), UpperBound::new(upper_bound)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +355,7 @@ impl RequiresPython {
|
|||
/// into the marker explicitly.
|
||||
pub(crate) fn simplify_markers(&self, marker: MarkerTree) -> MarkerTree {
|
||||
let (lower, upper) = (self.range().lower(), self.range().upper());
|
||||
marker.simplify_python_versions(lower.0.as_ref(), upper.0.as_ref())
|
||||
marker.simplify_python_versions(lower.as_ref(), upper.as_ref())
|
||||
}
|
||||
|
||||
/// The inverse of `simplify_markers`.
|
||||
|
|
@ -374,7 +375,7 @@ impl RequiresPython {
|
|||
/// ```
|
||||
pub(crate) fn complexify_markers(&self, marker: MarkerTree) -> MarkerTree {
|
||||
let (lower, upper) = (self.range().lower(), self.range().upper());
|
||||
marker.complexify_python_versions(lower.0.as_ref(), upper.0.as_ref())
|
||||
marker.complexify_python_versions(lower.as_ref(), upper.as_ref())
|
||||
}
|
||||
|
||||
/// Returns `false` if the wheel's tags state it can't be used in the given Python version
|
||||
|
|
@ -623,276 +624,6 @@ impl SimplifiedMarkerTree {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LowerBound(Bound<Version>);
|
||||
|
||||
impl LowerBound {
|
||||
/// Initialize a [`LowerBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`LowerBound`] truncated to the major and minor version.
|
||||
fn major_minor(&self) -> Self {
|
||||
match &self.0 {
|
||||
// Ex) `>=3.10.1` -> `>=3.10`
|
||||
Bound::Included(version) => Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
))),
|
||||
// Ex) `>3.10.1` -> `>=3.10`.
|
||||
Bound::Excluded(version) => Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
))),
|
||||
Bound::Unbounded => Self(Bound::Unbounded),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the lower bound contains the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
match self.0 {
|
||||
Bound::Included(ref bound) => bound <= version,
|
||||
Bound::Excluded(ref bound) => bound < version,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifier`] for the lower bound.
|
||||
pub fn specifier(&self) -> Option<VersionSpecifier> {
|
||||
match &self.0 {
|
||||
Bound::Included(version) => Some(VersionSpecifier::greater_than_equal_version(
|
||||
version.clone(),
|
||||
)),
|
||||
Bound::Excluded(version) => {
|
||||
Some(VersionSpecifier::greater_than_version(version.clone()))
|
||||
}
|
||||
Bound::Unbounded => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for LowerBound {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// See: <https://github.com/pubgrub-rs/pubgrub/blob/4b4b44481c5f93f3233221dc736dd23e67e00992/src/range.rs#L324>
|
||||
impl Ord for LowerBound {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let left = self.0.as_ref();
|
||||
let right = other.0.as_ref();
|
||||
|
||||
match (left, right) {
|
||||
// left: ∞-----
|
||||
// right: ∞-----
|
||||
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
|
||||
// left: [---
|
||||
// right: ∞-----
|
||||
(Bound::Included(_left), Bound::Unbounded) => Ordering::Greater,
|
||||
// left: ]---
|
||||
// right: ∞-----
|
||||
(Bound::Excluded(_left), Bound::Unbounded) => Ordering::Greater,
|
||||
// left: ∞-----
|
||||
// right: [---
|
||||
(Bound::Unbounded, Bound::Included(_right)) => Ordering::Less,
|
||||
// left: [----- OR [----- OR [-----
|
||||
// right: [--- OR [----- OR [---
|
||||
(Bound::Included(left), Bound::Included(right)) => left.cmp(right),
|
||||
(Bound::Excluded(left), Bound::Included(right)) => match left.cmp(right) {
|
||||
// left: ]-----
|
||||
// right: [---
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: ]-----
|
||||
// right: [---
|
||||
Ordering::Equal => Ordering::Greater,
|
||||
// left: ]---
|
||||
// right: [-----
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
// left: ∞-----
|
||||
// right: ]---
|
||||
(Bound::Unbounded, Bound::Excluded(_right)) => Ordering::Less,
|
||||
(Bound::Included(left), Bound::Excluded(right)) => match left.cmp(right) {
|
||||
// left: [-----
|
||||
// right: ]---
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: [-----
|
||||
// right: ]---
|
||||
Ordering::Equal => Ordering::Less,
|
||||
// left: [---
|
||||
// right: ]-----
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
// left: ]----- OR ]----- OR ]---
|
||||
// right: ]--- OR ]----- OR ]-----
|
||||
(Bound::Excluded(left), Bound::Excluded(right)) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LowerBound {
|
||||
fn default() -> Self {
|
||||
Self(Bound::Unbounded)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LowerBound {
|
||||
type Target = Bound<Version>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LowerBound> for Bound<Version> {
|
||||
fn from(bound: LowerBound) -> Self {
|
||||
bound.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct UpperBound(Bound<Version>);
|
||||
|
||||
impl UpperBound {
|
||||
/// Initialize a [`UpperBound`] with the given bound.
|
||||
///
|
||||
/// These bounds use release-only semantics when comparing versions.
|
||||
pub fn new(bound: Bound<Version>) -> Self {
|
||||
Self(match bound {
|
||||
Bound::Included(version) => Bound::Included(version.only_release()),
|
||||
Bound::Excluded(version) => Bound::Excluded(version.only_release()),
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`UpperBound`] truncated to the major and minor version.
|
||||
fn major_minor(&self) -> Self {
|
||||
match &self.0 {
|
||||
// Ex) `<=3.10.1` -> `<=3.10`
|
||||
Bound::Included(version) => Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
))),
|
||||
// Ex) `<3.10.1` -> `<=3.10` (but `<3.10.0` is `<3.10`)
|
||||
Bound::Excluded(version) => {
|
||||
if version.release().get(2).is_some_and(|patch| *patch > 0) {
|
||||
Self(Bound::Included(Version::new(
|
||||
version.release().iter().take(2),
|
||||
)))
|
||||
} else {
|
||||
Self(Bound::Excluded(Version::new(
|
||||
version.release().iter().take(2),
|
||||
)))
|
||||
}
|
||||
}
|
||||
Bound::Unbounded => Self(Bound::Unbounded),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the upper bound contains the given version.
|
||||
pub fn contains(&self, version: &Version) -> bool {
|
||||
match self.0 {
|
||||
Bound::Included(ref bound) => bound >= version,
|
||||
Bound::Excluded(ref bound) => bound > version,
|
||||
Bound::Unbounded => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`VersionSpecifier`] for the upper bound.
|
||||
pub fn specifier(&self) -> Option<VersionSpecifier> {
|
||||
match &self.0 {
|
||||
Bound::Included(version) => {
|
||||
Some(VersionSpecifier::less_than_equal_version(version.clone()))
|
||||
}
|
||||
Bound::Excluded(version) => Some(VersionSpecifier::less_than_version(version.clone())),
|
||||
Bound::Unbounded => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for UpperBound {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
/// See: <https://github.com/pubgrub-rs/pubgrub/blob/4b4b44481c5f93f3233221dc736dd23e67e00992/src/range.rs#L324>
|
||||
impl Ord for UpperBound {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let left = self.0.as_ref();
|
||||
let right = other.0.as_ref();
|
||||
|
||||
match (left, right) {
|
||||
// left: -----∞
|
||||
// right: -----∞
|
||||
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
|
||||
// left: ---]
|
||||
// right: -----∞
|
||||
(Bound::Included(_left), Bound::Unbounded) => Ordering::Less,
|
||||
// left: ---[
|
||||
// right: -----∞
|
||||
(Bound::Excluded(_left), Bound::Unbounded) => Ordering::Less,
|
||||
// left: -----∞
|
||||
// right: ---]
|
||||
(Bound::Unbounded, Bound::Included(_right)) => Ordering::Greater,
|
||||
// left: -----] OR -----] OR ---]
|
||||
// right: ---] OR -----] OR -----]
|
||||
(Bound::Included(left), Bound::Included(right)) => left.cmp(right),
|
||||
(Bound::Excluded(left), Bound::Included(right)) => match left.cmp(right) {
|
||||
// left: ---[
|
||||
// right: -----]
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: -----[
|
||||
// right: -----]
|
||||
Ordering::Equal => Ordering::Less,
|
||||
// left: -----[
|
||||
// right: ---]
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
(Bound::Unbounded, Bound::Excluded(_right)) => Ordering::Greater,
|
||||
(Bound::Included(left), Bound::Excluded(right)) => match left.cmp(right) {
|
||||
// left: ---]
|
||||
// right: -----[
|
||||
Ordering::Less => Ordering::Less,
|
||||
// left: -----]
|
||||
// right: -----[
|
||||
Ordering::Equal => Ordering::Greater,
|
||||
// left: -----]
|
||||
// right: ---[
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
},
|
||||
// left: -----[ OR -----[ OR ---[
|
||||
// right: ---[ OR -----[ OR -----[
|
||||
(Bound::Excluded(left), Bound::Excluded(right)) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UpperBound {
|
||||
fn default() -> Self {
|
||||
Self(Bound::Unbounded)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for UpperBound {
|
||||
type Target = Bound<Version>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UpperBound> for Bound<Version> {
|
||||
fn from(bound: UpperBound) -> Self {
|
||||
bound.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cmp::Ordering;
|
||||
|
|
@ -900,9 +631,8 @@ mod tests {
|
|||
use std::str::FromStr;
|
||||
|
||||
use uv_distribution_filename::WheelFilename;
|
||||
use uv_pep440::{Version, VersionSpecifiers};
|
||||
use uv_pep440::{LowerBound, UpperBound, Version, VersionSpecifiers};
|
||||
|
||||
use crate::requires_python::{LowerBound, UpperBound};
|
||||
use crate::RequiresPython;
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -617,10 +617,10 @@ mod tests {
|
|||
use std::ops::Bound;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use uv_pep440::Version;
|
||||
use uv_pep440::{LowerBound, UpperBound, Version};
|
||||
use uv_pep508::{MarkerEnvironment, MarkerEnvironmentBuilder};
|
||||
|
||||
use crate::requires_python::{LowerBound, RequiresPython, RequiresPythonRange, UpperBound};
|
||||
use crate::requires_python::{RequiresPython, RequiresPythonRange};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue