From f3a24b95085347d9b9d31ebfbec5e30bba4365b9 Mon Sep 17 00:00:00 2001 From: Sebastian Bugge Date: Fri, 18 Apr 2025 13:51:06 +0200 Subject: [PATCH 1/4] Remove uneeded code. --- src/lib.rs | 24 --- src/version.rs | 231 +------------------------- src/version/tests.rs | 378 +------------------------------------------ 3 files changed, 4 insertions(+), 629 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d8ad29c..ca0ec39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -856,10 +856,6 @@ impl Release { pub fn is_retired(&self) -> bool { self.retirement_status.is_some() } - - fn is_pre(&self) -> bool { - self.version.is_pre() - } } #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] @@ -926,26 +922,6 @@ pub struct Dependency { pub repository: Option, } -impl Dependency { - pub(crate) fn from_range(range: Range) -> Self { - Dependency { - app: None, - optional: false, - repository: None, - requirement: range, - } - } - - pub(crate) fn from_version(version: &Version) -> Self { - Dependency { - app: None, - optional: false, - repository: None, - requirement: Range::new(version.to_string()), - } - } -} - static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), " (", env!("CARGO_PKG_VERSION"), ")"); fn validate_package_and_version(package: &str, version: &str) -> Result<(), ApiError> { diff --git a/src/version.rs b/src/version.rs index 34de180..8f6bbab 100644 --- a/src/version.rs +++ b/src/version.rs @@ -2,19 +2,9 @@ //! and compatible with the Elixir Version module, which is used by Hex //! internally as well as be the Elixir build tool Hex client. -use std::{ - borrow::Borrow, cell::RefCell, cmp::Ordering, collections::HashMap, convert::TryFrom, - error::Error as StdError, fmt, -}; - -use crate::{Dependency, Package, Release}; +use std::{cmp::Ordering, convert::TryFrom, fmt}; use self::parser::Parser; -use pubgrub::{ - error::PubGrubError, - solver::{Dependencies, choose_package_with_fewest_versions}, - type_aliases::Map, -}; use serde::{ Deserialize, Serialize, de::{self, Deserializer}, @@ -27,8 +17,6 @@ mod parser; #[cfg(test)] mod tests; -type PubgrubRange = pubgrub::range::Range; - /// In a nutshell, a version is represented by three numbers: /// /// MAJOR.MINOR.PATCH @@ -408,220 +396,3 @@ impl std::cmp::Ord for PreOrder<'_> { } } } - -pub type PackageVersions = HashMap; - -pub type ResolutionError = PubGrubError; - -pub fn resolve_versions( - remote: Box, - root_name: PackageName, - dependencies: Requirements, - locked: &HashMap, -) -> Result -where - Requirements: Iterator, -{ - let root_version = Version::new(0, 0, 0); - let root = Package { - name: root_name.clone(), - repository: "local".to_string(), - releases: vec![Release { - version: root_version.clone(), - outer_checksum: vec![], - retirement_status: None, - requirements: root_dependencies(dependencies, locked)?, - meta: (), - }], - }; - let packages = pubgrub::solver::resolve( - &DependencyProvider::new(remote, root, locked), - root_name.clone(), - root_version, - )? - .into_iter() - .filter(|(name, _)| name.as_str() != root_name.as_str()) - .collect(); - - Ok(packages) -} - -fn root_dependencies( - base_requirements: Requirements, - locked: &HashMap, -) -> Result, ResolutionError> -where - Requirements: Iterator, -{ - // Record all of the already locked versions as hard requirements - let mut requirements: HashMap<_, _> = locked - .iter() - .map(|(name, version)| (name.to_string(), Dependency::from_version(version))) - .collect(); - - for (name, range) in base_requirements { - match locked.get(&name) { - // If the package was not already locked then we can use the - // specified version requirement without modification. - None => { - let _ = requirements.insert(name, Dependency::from_range(range)); - } - - // If the version was locked we verify that the requirement is - // compatible with the locked version. - Some(locked_version) => { - let compatible = range - .to_pubgrub() - .map_err(|e| ResolutionError::Failure(format!("Failed to parse range {}", e)))? - .contains(locked_version); - if !compatible { - return Err(ResolutionError::Failure(format!( - "{package} is specified with the requirement `{requirement}`, \ -but it is locked to {version}, which is incompatible.", - package = name, - requirement = range, - version = locked_version, - ))); - } - } - }; - } - - Ok(requirements) - // let locked = locked - // .iter() - // .map(|(name, version)| (name.to_string(), Range::new(version.to_string()))); - // // Add the locked versions as new requirements that override any existing - // // entry in the dependencies list. Collection into a HashMap is used for - // // de-duplication. - // let deps: HashMap<_, _> = dependencies.chain(locked).collect(); - // deps.into_iter() - // .map(|(package, requirement)| { - // ( - // package, - // Dependency { - // app: None, - // optional: false, - // repository: None, - // requirement, - // }, - // ) - // }) - // .collect() -} - -pub trait PackageFetcher { - fn get_dependencies(&self, package: &str) -> Result>; -} - -struct DependencyProvider<'a> { - packages: RefCell>, - remote: Box, - locked: &'a HashMap, -} - -impl<'a> DependencyProvider<'a> { - fn new( - remote: Box, - root: Package, - locked: &'a HashMap, - ) -> Self { - let mut packages = HashMap::new(); - let _ = packages.insert(root.name.clone(), root); - Self { - packages: RefCell::new(packages), - locked, - remote, - } - } - - /// Download information about the package from the registry into the local - /// store. Does nothing if the packages are already known. - /// - /// Package versions are sorted from newest to oldest, with all pre-releases - /// at the end to ensure that a non-prerelease version will be picked first - /// if there is one. - // - fn ensure_package_fetched( - // We would like to use `&mut self` but the pubgrub library enforces - // `&self` with interop mutability. - &self, - name: &str, - ) -> Result<(), Box> { - let mut packages = self.packages.borrow_mut(); - if packages.get(name).is_none() { - let mut package = self.remote.get_dependencies(name)?; - // Sort the packages from newest to oldest, pres after all others - package.releases.sort_by(|a, b| a.version.cmp(&b.version)); - package.releases.reverse(); - let (pre, mut norm): (_, Vec<_>) = - package.releases.into_iter().partition(Release::is_pre); - norm.extend(pre); - package.releases = norm; - packages.insert(name.to_string(), package); - } - Ok(()) - } -} - -type PackageName = String; - -impl pubgrub::solver::DependencyProvider for DependencyProvider<'_> { - fn choose_package_version< - Name: Borrow, - Ver: Borrow>, - >( - &self, - potential_packages: impl Iterator, - ) -> Result<(Name, Option), Box> { - let potential_packages: Vec<_> = potential_packages - .map::>, _>(|pair| { - self.ensure_package_fetched(pair.0.borrow())?; - Ok(pair) - }) - .collect::>()?; - let list_available_versions = |name: &String| { - self.packages - .borrow() - .get(name) - .cloned() - .into_iter() - .flat_map(|p| p.releases.into_iter()) - .map(|p| p.version) - }; - Ok(choose_package_with_fewest_versions( - list_available_versions, - potential_packages.into_iter(), - )) - } - - fn get_dependencies( - &self, - name: &PackageName, - version: &Version, - ) -> Result, Box> { - self.ensure_package_fetched(name)?; - let packages = self.packages.borrow(); - let release = match packages - .get(name) - .into_iter() - .flat_map(|p| p.releases.iter()) - .find(|r| &r.version == version) - { - Some(release) => release, - None => return Ok(Dependencies::Unknown), - }; - - // Only use retired versions if they have been locked - if release.is_retired() && self.locked.get(name) != Some(version) { - return Ok(Dependencies::Unknown); - } - - let mut deps: Map = Default::default(); - for (name, d) in &release.requirements { - let range = d.requirement.to_pubgrub()?; - deps.insert(name.clone(), range); - } - Ok(Dependencies::Known(deps)) - } -} diff --git a/src/version/tests.rs b/src/version/tests.rs index 3b10b45..c4d500c 100644 --- a/src/version/tests.rs +++ b/src/version/tests.rs @@ -1,13 +1,8 @@ -use std::{ - cmp::Ordering::{Equal, Greater, Less}, - collections::HashMap, -}; +use std::cmp::Ordering::{Equal, Greater, Less}; use parser::Error; use pubgrub::version::Version as _; -use crate::{ApiError, Release, RetirementReason, RetirementStatus}; - use super::{ Identifier::{AlphaNumeric, Numeric}, *, @@ -416,373 +411,6 @@ fn test_pubgrub_bump_prerelease_ending_with_a_letter() { ); } -struct Remote { - deps: HashMap, -} - -impl PackageFetcher for Remote { - fn get_dependencies(&self, package: &str) -> Result> { - self.deps - .get(package) - .cloned() - .ok_or(Box::new(ApiError::NotFound)) - } -} - -fn make_remote() -> Box { - let mut deps = HashMap::new(); - deps.insert( - "gleam_stdlib".to_string(), - Package { - name: "gleam_stdlib".to_string(), - repository: "hexpm".to_string(), - releases: vec![ - Release { - version: Version::try_from("0.1.0").unwrap(), - requirements: [].into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - Release { - version: Version::try_from("0.2.0").unwrap(), - requirements: [].into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - Release { - version: Version::try_from("0.2.2").unwrap(), - requirements: [].into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - Release { - version: Version::try_from("0.3.0").unwrap(), - requirements: [].into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - ], - }, - ); - deps.insert( - "gleam_otp".to_string(), - Package { - name: "gleam_otp".to_string(), - repository: "hexpm".to_string(), - releases: vec![ - Release { - version: Version::try_from("0.1.0").unwrap(), - requirements: [( - "gleam_stdlib".to_string(), - Dependency { - app: None, - optional: false, - repository: None, - requirement: Range::new(">= 0.1.0".to_string()), - }, - )] - .into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - Release { - version: Version::try_from("0.2.0").unwrap(), - requirements: [( - "gleam_stdlib".to_string(), - Dependency { - app: None, - optional: false, - repository: None, - requirement: Range::new(">= 0.1.0".to_string()), - }, - )] - .into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - Release { - version: Version::try_from("0.3.0-rc1").unwrap(), - requirements: [( - "gleam_stdlib".to_string(), - Dependency { - app: None, - optional: false, - repository: None, - requirement: Range::new(">= 0.1.0".to_string()), - }, - )] - .into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - ], - }, - ); - deps.insert( - "package_with_retired".to_string(), - Package { - name: "package_with_retired".to_string(), - repository: "hexpm".to_string(), - releases: vec![ - Release { - version: Version::try_from("0.1.0").unwrap(), - requirements: [].into(), - retirement_status: None, - outer_checksum: vec![1, 2, 3], - meta: (), - }, - Release { - version: Version::try_from("0.2.0").unwrap(), - requirements: [].into(), - retirement_status: Some(RetirementStatus { - reason: RetirementReason::Security, - message: "It's bad".to_string(), - }), - outer_checksum: vec![1, 2, 3], - meta: (), - }, - ], - }, - ); - Box::new(Remote { deps }) -} - -#[test] -fn resolution_with_locked() { - let locked_stdlib = ("gleam_stdlib".to_string(), Version::parse("0.1.0").unwrap()); - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![("gleam_stdlib".to_string(), Range::new("~> 0.1".to_string()))].into_iter(), - &vec![locked_stdlib].into_iter().collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![("gleam_stdlib".to_string(), Version::parse("0.1.0").unwrap())] - .into_iter() - .collect() - ); -} - -#[test] -fn resolution_without_deps() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![].into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap(); - assert_eq!(result, vec![].into_iter().collect()) -} - -#[test] -fn resolution_1_dep() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![("gleam_stdlib".to_string(), Range::new("~> 0.1".to_string()))].into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![( - "gleam_stdlib".to_string(), - Version::try_from("0.3.0").unwrap() - )] - .into_iter() - .collect() - ); -} - -#[test] -fn resolution_with_nested_deps() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![("gleam_otp".to_string(), Range::new("~> 0.1".to_string()))].into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![ - ("gleam_otp".to_string(), Version::try_from("0.2.0").unwrap()), - ( - "gleam_stdlib".to_string(), - Version::try_from("0.3.0").unwrap() - ) - ] - .into_iter() - .collect() - ); -} - -#[test] -fn resolution_locked_to_older_version() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![("gleam_otp".to_string(), Range::new("~> 0.1.0".to_string()))].into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![ - ("gleam_otp".to_string(), Version::try_from("0.1.0").unwrap()), - ( - "gleam_stdlib".to_string(), - Version::try_from("0.3.0").unwrap() - ) - ] - .into_iter() - .collect() - ); -} - -#[test] -fn resolution_retired_versions_not_used_by_default() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![( - "package_with_retired".to_string(), - Range::new("> 0.0.0".to_string()), - )] - .into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![( - "package_with_retired".to_string(), - // Uses the older version that hasn't been retired - Version::try_from("0.1.0").unwrap() - ),] - .into_iter() - .collect() - ); -} - -#[test] -fn resolution_retired_versions_can_be_used_if_locked() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![( - "package_with_retired".to_string(), - Range::new("> 0.0.0".to_string()), - )] - .into_iter(), - &vec![("package_with_retired".to_string(), Version::new(0, 2, 0))] - .into_iter() - .collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![( - "package_with_retired".to_string(), - // Uses the locked version even though it's retired - Version::new(0, 2, 0) - ),] - .into_iter() - .collect() - ); -} - -#[test] -fn resolution_prerelease_can_be_selected() { - let result = resolve_versions( - make_remote(), - "app".to_string(), - vec![( - "gleam_otp".to_string(), - Range::new("~> 0.3.0-rc1".to_string()), - )] - .into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap(); - assert_eq!( - result, - vec![ - ( - "gleam_otp".to_string(), - Version::try_from("0.3.0-rc1").unwrap() - ), - ( - "gleam_stdlib".to_string(), - Version::try_from("0.3.0").unwrap() - ), - ] - .into_iter() - .collect(), - ); -} - -#[test] -fn resolution_not_found_dep() { - resolve_versions( - make_remote(), - "app".to_string(), - vec![("unknown".to_string(), Range::new("~> 0.1".to_string()))].into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap_err(); -} - -#[test] -fn resolution_no_matching_version() { - resolve_versions( - make_remote(), - "app".to_string(), - vec![( - "gleam_stdlib".to_string(), - Range::new("~> 99.0".to_string()), - )] - .into_iter(), - &vec![].into_iter().collect(), - ) - .unwrap_err(); -} - -#[test] -fn resolution_locked_version_doesnt_satisfy_requirements() { - let err = resolve_versions( - make_remote(), - "app".to_string(), - vec![( - "gleam_stdlib".to_string(), - Range::new("~> 0.1.0".to_string()), - )] - .into_iter(), - &vec![("gleam_stdlib".into(), Version::new(0, 2, 0))] - .into_iter() - .collect(), - ) - .unwrap_err(); - - match err { - PubGrubError::Failure(msg) => assert_eq!( - msg, - "gleam_stdlib is specified with the requirement `~> 0.1.0`, but it is locked to 0.2.0, which is incompatible." - ), - _ => panic!("wrong error: {}", err), - } -} - #[test] fn manifest_toml() { let manifest = toml::to_string( @@ -809,7 +437,7 @@ fn manifest_toml() { ), ] .into_iter() - .collect::(), + .collect::>(), ) .unwrap(); let expected1 = r#"thingy = "0.1.0" @@ -818,7 +446,7 @@ gleam_stdlib = "0.17.1" let expected2 = r#"gleam_stdlib = "0.17.1" thingy = "0.1.0" "#; - assert!(&manifest == expected1 || &manifest == expected2); + assert!(manifest == expected1 || manifest == expected2); } #[test] From 05658445223df14d072c8d312cff9283e2c99dc6 Mon Sep 17 00:00:00 2001 From: Sebastian Bugge Date: Fri, 18 Apr 2025 14:10:34 +0200 Subject: [PATCH 2/4] Parse range on creation. --- src/lib.rs | 3 ++- src/tests.rs | 4 ++-- src/version.rs | 23 +++++++++++++---------- src/version/tests.rs | 1 + 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ca0ec39..3d95d18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -790,7 +790,8 @@ fn proto_to_retirement_reason(reason: proto::package::RetirementReason) -> Retir fn proto_to_dep(dep: proto::package::Dependency) -> Result<(String, Dependency), ApiError> { let app = dep.app; let repository = dep.repository; - let requirement = Range::new(dep.requirement); + let requirement = Range::new(dep.requirement.clone()) + .map_err(|_| ApiError::InvalidVersionFormat(dep.requirement))?; Ok(( dep.package, Dependency { diff --git a/src/tests.rs b/src/tests.rs index fb62ede..3f89d16 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1221,7 +1221,7 @@ async fn get_package_release_ok() { ( "plug".into(), Dependency { - requirement: Range::new("~>0.11.0".into()), + requirement: Range::new("~>0.11.0".into()).unwrap(), optional: false, app: Some("plug".into()), repository: None @@ -1230,7 +1230,7 @@ async fn get_package_release_ok() { ( "cowboy".into(), Dependency { - requirement: Range::new("~>1.0.0".into()), + requirement: Range::new("~>1.0.0".into()).unwrap(), optional: false, app: Some("cowboy".into()), repository: None diff --git a/src/version.rs b/src/version.rs index 8f6bbab..9a0f589 100644 --- a/src/version.rs +++ b/src/version.rs @@ -315,27 +315,31 @@ impl Identifier { } #[derive(Clone, PartialEq, Eq)] -pub struct Range(String); +pub struct Range { + spec: String, + range: pubgrub::range::Range, +} impl Range { - pub fn new(spec: String) -> Self { - Self(spec) + pub fn new(spec: String) -> Result { + let range = Version::parse_range(spec.as_str())?; + Ok(Self { spec, range }) } } impl Range { - pub fn to_pubgrub(&self) -> Result, parser::Error> { - Version::parse_range(&self.0) + pub fn to_pubgrub(&self) -> &pubgrub::range::Range { + &self.range } pub fn as_str(&self) -> &str { - &self.0 + &self.spec } } impl fmt::Debug for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Range").field(&self.0.to_string()).finish() + f.debug_tuple("Range").field(&self.spec).finish() } } @@ -345,7 +349,7 @@ impl<'de> Deserialize<'de> for Range { D: Deserializer<'de>, { let s: &str = Deserialize::deserialize(deserializer)?; - Ok(Range::new(s.to_string())) + Range::new(s.to_string()).map_err(serde::de::Error::custom) } } @@ -360,8 +364,7 @@ impl Serialize for Range { impl fmt::Display for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0)?; - Ok(()) + f.write_str(&self.spec) } } diff --git a/src/version/tests.rs b/src/version/tests.rs index c4d500c..0f7b95b 100644 --- a/src/version/tests.rs +++ b/src/version/tests.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering::{Equal, Greater, Less}; +use std::collections::HashMap; use parser::Error; use pubgrub::version::Version as _; From 39642f1707b35edf7ea5a129030119ecbfc09765 Mon Sep 17 00:00:00 2001 From: Sebastian Bugge Date: Fri, 18 Apr 2025 14:19:41 +0200 Subject: [PATCH 3/4] Allow convertions from Version and pubgrub::range::Range. --- src/version.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/version.rs b/src/version.rs index 9a0f589..52fb8f2 100644 --- a/src/version.rs +++ b/src/version.rs @@ -337,6 +337,19 @@ impl Range { } } +impl From> for Range { + fn from(range: pubgrub::range::Range) -> Self { + let spec = range.to_string(); + Self { spec, range } + } +} + +impl From for Range { + fn from(version: Version) -> Self { + pubgrub::range::Range::exact(version).into() + } +} + impl fmt::Debug for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Range").field(&self.spec).finish() From e28d73ff6cccf5afd2b19ad38b53b919ea6f8331 Mon Sep 17 00:00:00 2001 From: Sebastian Bugge Date: Fri, 18 Apr 2025 19:45:04 +0200 Subject: [PATCH 4/4] Upgrade pubgrub version. --- Cargo.toml | 2 +- src/version.rs | 134 ++++++++---------------------------------- src/version/parser.rs | 6 +- src/version/tests.rs | 43 ++++---------- 4 files changed, 41 insertions(+), 144 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 25b7b7d..8dcb5e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ ring = "0.17" # PEM -> DER conversion x509-parser = "0.15" # Pubgrub dependency resolution algorithm -pubgrub = "0.2" +pubgrub = "0.3" # Basic auth HTTP helper http-auth-basic = "0.3" # base16 encoding diff --git a/src/version.rs b/src/version.rs index 52fb8f2..5912291 100644 --- a/src/version.rs +++ b/src/version.rs @@ -10,8 +10,6 @@ use serde::{ de::{self, Deserializer}, }; -pub use pubgrub::report as pubgrub_report; - mod lexer; mod parser; #[cfg(test)] @@ -101,7 +99,7 @@ impl Version { } /// Parse a Hex compatible version range. i.e. `> 1 and < 2 or == 4.5.2`. - fn parse_range(input: &str) -> Result, parser::Error> { + fn parse_range(input: &str) -> Result, parser::Error> { let mut parser = Parser::new(input)?; let version = parser.range()?; if !parser.is_eof() { @@ -117,6 +115,10 @@ impl Version { Ok(version) } + pub fn lowest() -> Self { + Self::new(0, 0, 0) + } + fn tuple(&self) -> (u32, u32, u32, PreOrder<'_>) { ( self.major, @@ -131,6 +133,21 @@ impl Version { } } +pub trait LowestVersion { + fn lowest_version(&self) -> Option; +} +impl LowestVersion for pubgrub::Range { + fn lowest_version(&self) -> Option { + self.iter() + .flat_map(|(lower, _higher)| match lower { + std::ops::Bound::Included(v) => Some(v.clone()), + std::ops::Bound::Excluded(_) => None, + std::ops::Bound::Unbounded => Some(Version::lowest()), + }) + .min() + } +} + impl<'de> Deserialize<'de> for Version { fn deserialize(deserializer: D) -> Result where @@ -162,104 +179,6 @@ impl std::cmp::Ord for Version { } } -impl pubgrub::version::Version for Version { - fn lowest() -> Self { - Self::new(0, 0, 0) - } - - fn bump(&self) -> Self { - if self.is_pre() { - let mut pre = self.pre.clone(); - let last_component = pre - .last_mut() - // This `.expect` is safe, as we know there to be at least - // one pre-release component. - .expect("no pre-release components"); - - match last_component { - Identifier::Numeric(pre) => *pre += 1, - Identifier::AlphaNumeric(pre) => { - let mut segments = split_alphanumeric(pre); - let last_segment = segments.last_mut().unwrap(); - - match last_segment { - AlphaOrNumeric::Numeric(n) => *n += 1, - AlphaOrNumeric::Alpha(alpha) => { - // We should potentially be smarter about this (for instance, pick the next letter in the - // alphabetic sequence), however, this seems like it could be quite a bit more complex. - alpha.push('1') - } - } - - *pre = segments - .into_iter() - .map(|segment| match segment { - AlphaOrNumeric::Alpha(segment) => segment, - AlphaOrNumeric::Numeric(segment) => segment.to_string(), - }) - .collect::>() - .join(""); - } - } - - Self { - major: self.major, - minor: self.minor, - patch: self.patch, - pre, - build: None, - } - } else { - self.bump_patch() - } - } -} - -enum AlphaOrNumeric { - Alpha(String), - Numeric(u32), -} - -/// Splits the given string into alphabetic and numeric segments. -fn split_alphanumeric(str: &str) -> Vec { - let mut segments = Vec::new(); - let mut current_segment = String::new(); - let mut previous_char_was_numeric = None; - - for char in str.chars() { - let is_numeric = char.is_ascii_digit(); - match previous_char_was_numeric { - Some(previous_char_was_numeric) if previous_char_was_numeric == is_numeric => { - current_segment.push(char) - } - _ => { - if !current_segment.is_empty() { - if current_segment.chars().any(|char| char.is_ascii_digit()) { - segments.push(AlphaOrNumeric::Numeric(current_segment.parse().unwrap())); - } else { - segments.push(AlphaOrNumeric::Alpha(current_segment)); - } - - current_segment = String::new(); - } - - current_segment.push(char); - previous_char_was_numeric = Some(is_numeric); - } - } - } - - if !current_segment.is_empty() { - if current_segment.chars().any(|char| char.is_ascii_digit()) { - segments.push(AlphaOrNumeric::Numeric(current_segment.parse().unwrap())); - } else { - segments.push(AlphaOrNumeric::Alpha(current_segment)); - } - } - - segments -} - impl<'a> TryFrom<&'a str> for Version { type Error = parser::Error; @@ -317,18 +236,18 @@ impl Identifier { #[derive(Clone, PartialEq, Eq)] pub struct Range { spec: String, - range: pubgrub::range::Range, + range: pubgrub::Range, } impl Range { pub fn new(spec: String) -> Result { - let range = Version::parse_range(spec.as_str())?; + let range = Version::parse_range(&spec)?; Ok(Self { spec, range }) } } impl Range { - pub fn to_pubgrub(&self) -> &pubgrub::range::Range { + pub fn to_pubgrub(&self) -> &pubgrub::Range { &self.range } @@ -337,16 +256,15 @@ impl Range { } } -impl From> for Range { - fn from(range: pubgrub::range::Range) -> Self { +impl From> for Range { + fn from(range: pubgrub::Range) -> Self { let spec = range.to_string(); Self { spec, range } } } - impl From for Range { fn from(version: Version) -> Self { - pubgrub::range::Range::exact(version).into() + pubgrub::Range::singleton(version).into() } } diff --git a/src/version/parser.rs b/src/version/parser.rs index 0d606a9..6d2752c 100644 --- a/src/version/parser.rs +++ b/src/version/parser.rs @@ -8,7 +8,7 @@ use super::lexer::{self, Lexer, Token}; use crate::version::{Identifier, Version}; use thiserror::Error; -type PubgrubRange = pubgrub::range::Range; +type PubgrubRange = pubgrub::Range; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Error)] pub enum Error { @@ -322,11 +322,11 @@ impl<'input> Parser<'input> { self.skip_whitespace()?; match self.peek() { None => break, - Some(Numeric(_)) => range = and(range, PubgrubRange::exact(self.version()?)), + Some(Numeric(_)) => range = and(range, PubgrubRange::singleton(self.version()?)), Some(Eq) => { self.pop()?; - range = and(range, PubgrubRange::exact(self.version()?)); + range = and(range, PubgrubRange::singleton(self.version()?)); } Some(NotEq) => { diff --git a/src/version/tests.rs b/src/version/tests.rs index 0f7b95b..414e1ed 100644 --- a/src/version/tests.rs +++ b/src/version/tests.rs @@ -2,7 +2,6 @@ use std::cmp::Ordering::{Equal, Greater, Less}; use std::collections::HashMap; use parser::Error; -use pubgrub::version::Version as _; use super::{ Identifier::{AlphaNumeric, Numeric}, @@ -208,17 +207,21 @@ fn v_(major: u32, minor: u32, patch: u32, pre: Vec, build: Option; +type PubgrubRange = pubgrub::Range; -parse_range_test!(leading_space, " 1.2.3", PubgrubRange::exact(v(1, 2, 3))); -parse_range_test!(trailing_space, "1.2.3 ", PubgrubRange::exact(v(1, 2, 3))); +parse_range_test!(leading_space, " 1.2.3", PubgrubRange::singleton(v(1, 2, 3))); +parse_range_test!( + trailing_space, + "1.2.3 ", + PubgrubRange::singleton(v(1, 2, 3)) +); -parse_range_test!(eq_triplet, "== 1.2.3 ", PubgrubRange::exact(v(1, 2, 3))); +parse_range_test!(eq_triplet, "== 1.2.3 ", PubgrubRange::singleton(v(1, 2, 3))); parse_range_test!( eq_triplet_nospace, "==1.2.3 ", - PubgrubRange::exact(v(1, 2, 3)) + PubgrubRange::singleton(v(1, 2, 3)) ); parse_range_test!( @@ -227,12 +230,12 @@ parse_range_test!( PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 2, 4))) ); -parse_range_test!(implicit_eq, "2.2.3", PubgrubRange::exact(v(2, 2, 3))); +parse_range_test!(implicit_eq, "2.2.3", PubgrubRange::singleton(v(2, 2, 3))); parse_range_test!( range_pre_build, "1.2.3-thing+oop", - PubgrubRange::exact(v_( + PubgrubRange::singleton(v_( 1, 2, 3, @@ -388,30 +391,6 @@ assert_order!(ord_pre_smaller_than_zero_flip, "1.0.0-rc1", Less, "1.0.0"); assert_order!(ord_pre_rc1_2, "1.0.0-rc1", Less, "1.0.0-rc2"); -#[test] -fn test_pubgrub_bump_patch() { - assert_eq!( - Version::parse("1.0.0").unwrap().bump(), - Version::parse("1.0.1").unwrap() - ); -} - -#[test] -fn test_pubgrub_bump_prerelease_ending_with_a_number() { - assert_eq!( - Version::parse("1.0.0-rc2").unwrap().bump(), - Version::parse("1.0.0-rc3").unwrap() - ); -} - -#[test] -fn test_pubgrub_bump_prerelease_ending_with_a_letter() { - assert_eq!( - Version::parse("1.0.0-rc2a").unwrap().bump(), - Version::parse("1.0.0-rc2a1").unwrap() - ); -} - #[test] fn manifest_toml() { let manifest = toml::to_string(