From 4adaa9a70012fb7bea3a201478d532a4dbed8fc1 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 2 Nov 2023 12:15:27 +0100 Subject: [PATCH] Wheel filename distribution package name (#278) The normalized name abstractions were not consistently, this PR uses them where they were previously missing: * `WheelFilename::distribution` * `Requirement::name` * `Requirement::extras` * `Metadata21::name` * `Metadata21::provides_dist` With `puffin-package` depending on `pep508_rs` this would be cyclical crate dependency, so `puffin-normalize` gets split out from `puffin-package`. `DistInfoName` has the same task and semantics as `PackageName`, so it's merged into the latter. `PackageName` and `ExtraName` documentation is moved onto the type and their constructors are called `new` instead of `normalize`. We now use these constructors rarely enough the implicit allocation by `to_string()` shouldn't matter anymore, while more actual cloning becomes visible. --- Cargo.lock | 27 ++++- Cargo.toml | 1 - crates/distribution-filename/Cargo.toml | 2 +- .../src/source_distribution.rs | 17 +-- crates/distribution-filename/src/wheel.rs | 7 +- crates/install-wheel-rs/src/wheel.rs | 6 +- crates/pep508-rs/Cargo.toml | 1 + crates/pep508-rs/src/lib.rs | 100 +++++++++++------- crates/puffin-cli/Cargo.toml | 1 + .../puffin-cli/src/commands/pip_uninstall.rs | 3 +- crates/puffin-cli/src/commands/reporters.rs | 6 +- crates/puffin-cli/src/main.rs | 5 +- crates/puffin-cli/src/requirements.rs | 4 +- crates/puffin-client/Cargo.toml | 1 + crates/puffin-client/src/client.rs | 8 +- crates/puffin-distribution/Cargo.toml | 1 + crates/puffin-distribution/src/lib.rs | 15 +-- crates/puffin-installer/Cargo.toml | 1 + crates/puffin-installer/src/plan.rs | 21 ++-- crates/puffin-installer/src/registry_index.rs | 2 +- crates/puffin-installer/src/site_packages.rs | 2 +- crates/puffin-installer/src/url_index.rs | 5 +- crates/puffin-normalize/Cargo.toml | 18 ++++ .../src/extra_name.rs | 53 +++------- crates/puffin-normalize/src/lib.rs | 5 + crates/puffin-normalize/src/package_name.rs | 74 +++++++++++++ crates/puffin-package/Cargo.toml | 2 +- crates/puffin-package/src/dist_info_name.rs | 77 -------------- crates/puffin-package/src/lib.rs | 3 - crates/puffin-package/src/package_name.rs | 89 ---------------- .../puffin-package/src/pypi_types/metadata.rs | 18 ++-- ...nts_txt__test__line-endings-basic.txt.snap | 24 +++-- ..._test__line-endings-constraints-a.txt.snap | 12 ++- ..._test__line-endings-constraints-b.txt.snap | 8 +- ...xt__test__line-endings-for-poetry.txt.snap | 20 +++- ...txt__test__line-endings-include-a.txt.snap | 8 +- ...txt__test__line-endings-include-b.txt.snap | 4 +- ...__line-endings-poetry-with-hashes.txt.snap | 20 +++- ...nts_txt__test__line-endings-small.txt.snap | 8 +- ...xt__test__line-endings-whitespace.txt.snap | 12 ++- ...quirements_txt__test__parse-basic.txt.snap | 24 +++-- ...ts_txt__test__parse-constraints-a.txt.snap | 12 ++- ...ts_txt__test__parse-constraints-b.txt.snap | 8 +- ...ments_txt__test__parse-for-poetry.txt.snap | 20 +++- ...ements_txt__test__parse-include-a.txt.snap | 8 +- ...ements_txt__test__parse-include-b.txt.snap | 4 +- ...t__test__parse-poetry-with-hashes.txt.snap | 20 +++- ...quirements_txt__test__parse-small.txt.snap | 8 +- ...ments_txt__test__parse-whitespace.txt.snap | 12 ++- crates/puffin-resolver/Cargo.toml | 1 + .../puffin-resolver/src/candidate_selector.rs | 5 +- crates/puffin-resolver/src/prerelease_mode.rs | 6 +- crates/puffin-resolver/src/pubgrub/mod.rs | 24 ++--- crates/puffin-resolver/src/pubgrub/package.rs | 5 +- .../puffin-resolver/src/pubgrub/priority.rs | 9 +- crates/puffin-resolver/src/resolution.rs | 6 +- crates/puffin-resolver/src/resolution_mode.rs | 4 +- crates/puffin-resolver/src/resolver.rs | 15 +-- crates/puffin-resolver/src/wheel_finder.rs | 8 +- crates/puffin-workspace/Cargo.toml | 2 +- crates/puffin-workspace/src/workspace.rs | 7 +- 61 files changed, 483 insertions(+), 416 deletions(-) create mode 100644 crates/puffin-normalize/Cargo.toml rename crates/{puffin-package => puffin-normalize}/src/extra_name.rs (69%) create mode 100644 crates/puffin-normalize/src/lib.rs create mode 100644 crates/puffin-normalize/src/package_name.rs delete mode 100644 crates/puffin-package/src/dist_info_name.rs delete mode 100644 crates/puffin-package/src/package_name.rs diff --git a/Cargo.lock b/Cargo.lock index fc3c3b56c..2451ddf19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -704,7 +704,7 @@ version = "0.0.1" dependencies = [ "pep440_rs 0.3.12", "platform-tags", - "puffin-package", + "puffin-normalize", "thiserror", "url", ] @@ -1737,6 +1737,7 @@ dependencies = [ "log", "once_cell", "pep440_rs 0.3.12", + "puffin-normalize", "pyo3", "pyo3-log", "regex", @@ -2010,6 +2011,7 @@ dependencies = [ "puffin-distribution", "puffin-installer", "puffin-interpreter", + "puffin-normalize", "puffin-package", "puffin-resolver", "puffin-workspace", @@ -2031,6 +2033,7 @@ version = "0.0.1" dependencies = [ "futures", "http-cache-reqwest", + "puffin-normalize", "puffin-package", "reqwest", "reqwest-middleware", @@ -2102,6 +2105,7 @@ dependencies = [ "anyhow", "pep440_rs 0.3.12", "puffin-cache", + "puffin-normalize", "puffin-package", "url", ] @@ -2121,6 +2125,7 @@ dependencies = [ "puffin-client", "puffin-distribution", "puffin-interpreter", + "puffin-normalize", "puffin-package", "rayon", "tempfile", @@ -2148,6 +2153,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "puffin-normalize" +version = "0.0.1" +dependencies = [ + "anyhow", + "indoc 2.0.4", + "insta", + "once_cell", + "regex", + "serde", + "serde_json", + "tempfile", + "test-case", +] + [[package]] name = "puffin-package" version = "0.0.1" @@ -2157,10 +2177,10 @@ dependencies = [ "indoc 2.0.4", "insta", "mailparse", - "memchr", "once_cell", "pep440_rs 0.3.12", "pep508_rs", + "puffin-normalize", "regex", "rfc2047-decoder", "serde", @@ -2199,6 +2219,7 @@ dependencies = [ "puffin-client", "puffin-distribution", "puffin-interpreter", + "puffin-normalize", "puffin-package", "puffin-traits", "sha2", @@ -2228,7 +2249,7 @@ dependencies = [ "fs-err", "pep440_rs 0.3.12", "pep508_rs", - "puffin-package", + "puffin-normalize", "pyproject-toml", "serde", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index e0420998c..326dbbd11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ indicatif = { version = "0.17.7" } indoc = { version = "2.0.4" } itertools = { version = "0.11.0" } mailparse = { version = "0.14.0" } -memchr = { version = "2.6.4" } miette = { version = "5.10.0" } once_cell = { version = "1.18.0" } petgraph = { version = "0.6.4" } diff --git a/crates/distribution-filename/Cargo.toml b/crates/distribution-filename/Cargo.toml index 9eea044cc..ad9fe963d 100644 --- a/crates/distribution-filename/Cargo.toml +++ b/crates/distribution-filename/Cargo.toml @@ -12,7 +12,7 @@ license = { workspace = true } [dependencies] pep440_rs = { path = "../pep440-rs" } platform-tags = { path = "../platform-tags" } -puffin-package = { path = "../puffin-package" } +puffin-normalize = { path = "../puffin-normalize" } thiserror = { workspace = true } url = { workspace = true } diff --git a/crates/distribution-filename/src/source_distribution.rs b/crates/distribution-filename/src/source_distribution.rs index 09003f996..1bd76774e 100644 --- a/crates/distribution-filename/src/source_distribution.rs +++ b/crates/distribution-filename/src/source_distribution.rs @@ -1,9 +1,11 @@ -use pep440_rs::Version; -use puffin_package::package_name::PackageName; use std::fmt::{Display, Formatter}; use std::str::FromStr; + use thiserror::Error; +use pep440_rs::Version; +use puffin_normalize::PackageName; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum SourceDistributionExtension { Zip, @@ -70,7 +72,7 @@ impl SourceDistributionFilename { }; if stem.len() <= package_name.as_ref().len() + "-".len() - || &PackageName::normalize(&stem[..package_name.as_ref().len()]) != package_name + || &PackageName::new(&stem[..package_name.as_ref().len()]) != package_name { return Err(SourceDistributionFilenameError::InvalidFilename { filename: filename.to_string(), @@ -111,8 +113,9 @@ pub enum SourceDistributionFilenameError { #[cfg(test)] mod tests { + use puffin_normalize::PackageName; + use crate::SourceDistributionFilename; - use puffin_package::package_name::PackageName; /// Only test already normalized names since the parsing is lossy #[test] @@ -123,7 +126,7 @@ mod tests { "foo-lib-1.2.3.tar.gz", ] { assert_eq!( - SourceDistributionFilename::parse(normalized, &PackageName::normalize("foo_lib")) + SourceDistributionFilename::parse(normalized, &PackageName::new("foo_lib")) .unwrap() .to_string(), normalized @@ -134,9 +137,7 @@ mod tests { #[test] fn errors() { for invalid in ["b-1.2.3.zip", "a-1.2.3-gamma.3.zip", "a-1.2.3.tar.zstd"] { - assert!( - SourceDistributionFilename::parse(invalid, &PackageName::normalize("a")).is_err() - ); + assert!(SourceDistributionFilename::parse(invalid, &PackageName::new("a")).is_err()); } } } diff --git a/crates/distribution-filename/src/wheel.rs b/crates/distribution-filename/src/wheel.rs index 372f46f01..eee7946b2 100644 --- a/crates/distribution-filename/src/wheel.rs +++ b/crates/distribution-filename/src/wheel.rs @@ -1,15 +1,16 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; -use pep440_rs::Version; use thiserror::Error; use url::Url; +use pep440_rs::Version; use platform_tags::Tags; +use puffin_normalize::PackageName; #[derive(Debug, Clone, Eq, PartialEq)] pub struct WheelFilename { - pub distribution: String, + pub distribution: PackageName, pub version: Version, pub python_tag: Vec, pub abi_tag: Vec, @@ -89,7 +90,7 @@ impl FromStr for WheelFilename { let version = Version::from_str(version) .map_err(|err| WheelFilenameError::InvalidVersion(filename.to_string(), err))?; Ok(WheelFilename { - distribution: distribution.to_string(), + distribution: PackageName::new(distribution), version, python_tag: python_tag.split('.').map(String::from).collect(), abi_tag: abi_tag.split('.').map(String::from).collect(), diff --git a/crates/install-wheel-rs/src/wheel.rs b/crates/install-wheel-rs/src/wheel.rs index 3345a3789..f1444d1a9 100644 --- a/crates/install-wheel-rs/src/wheel.rs +++ b/crates/install-wheel-rs/src/wheel.rs @@ -899,7 +899,7 @@ pub fn install_wheel( sys_executable: impl AsRef, ) -> Result { let name = &filename.distribution; - let _my_span = span!(Level::DEBUG, "install_wheel", name = name.as_str()); + let _my_span = span!(Level::DEBUG, "install_wheel", name = name.as_ref()); let base_location = location.venv_root(); @@ -918,12 +918,12 @@ pub fn install_wheel( .join("site-packages") }; - debug!(name = name.as_str(), "Opening zip"); + debug!(name = name.as_ref(), "Opening zip"); // No BufReader: https://github.com/zip-rs/zip/issues/381 let mut archive = ZipArchive::new(reader).map_err(|err| Error::from_zip_error("(index)".to_string(), err))?; - debug!(name = name.as_str(), "Getting wheel metadata"); + debug!(name = name.as_ref(), "Getting wheel metadata"); let dist_info_prefix = find_dist_info(filename, &mut archive)?; let (name, _version) = read_metadata(&dist_info_prefix, &mut archive)?; // TODO: Check that name and version match diff --git a/crates/pep508-rs/Cargo.toml b/crates/pep508-rs/Cargo.toml index a7cf5f53f..4db6df9a5 100644 --- a/crates/pep508-rs/Cargo.toml +++ b/crates/pep508-rs/Cargo.toml @@ -18,6 +18,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] pep440_rs = { path = "../pep440-rs" } +puffin-normalize = { path = "../puffin-normalize" } once_cell = { workspace = true } regex = { workspace = true } diff --git a/crates/pep508-rs/src/lib.rs b/crates/pep508-rs/src/lib.rs index f25c8e7f8..4482e9654 100644 --- a/crates/pep508-rs/src/lib.rs +++ b/crates/pep508-rs/src/lib.rs @@ -6,31 +6,16 @@ //! ``` //! use std::str::FromStr; //! use pep508_rs::Requirement; +//! use puffin_normalize::ExtraName; //! //! let marker = r#"requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8""#; //! let dependency_specification = Requirement::from_str(marker).unwrap(); -//! assert_eq!(dependency_specification.name, "requests"); -//! assert_eq!(dependency_specification.extras, Some(vec!["security".to_string(), "tests".to_string()])); +//! assert_eq!(dependency_specification.name.as_ref(), "requests"); +//! assert_eq!(dependency_specification.extras, Some(vec![ExtraName::new("security"), ExtraName::new("tests")])); //! ``` #![deny(missing_docs)] -mod marker; - -pub use marker::{ - MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, - MarkerValueString, MarkerValueVersion, MarkerWarningKind, StringVersion, -}; -#[cfg(feature = "pyo3")] -use pep440_rs::PyVersion; -use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; -#[cfg(feature = "pyo3")] -use pyo3::{ - basic::CompareOp, create_exception, exceptions::PyNotImplementedError, pyclass, pymethods, - pymodule, types::PyModule, IntoPy, PyObject, PyResult, Python, -}; -#[cfg(feature = "serde")] -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "pyo3")] use std::collections::hash_map::DefaultHasher; use std::collections::HashSet; @@ -38,10 +23,29 @@ use std::fmt::{Display, Formatter}; #[cfg(feature = "pyo3")] use std::hash::{Hash, Hasher}; use std::str::{Chars, FromStr}; + +#[cfg(feature = "pyo3")] +use pep440_rs::PyVersion; +#[cfg(feature = "pyo3")] +use pyo3::{ + create_exception, exceptions::PyNotImplementedError, pyclass, pyclass::CompareOp, pymethods, + pymodule, types::PyModule, IntoPy, PyObject, PyResult, Python, +}; +#[cfg(feature = "serde")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use thiserror::Error; use unicode_width::UnicodeWidthStr; use url::Url; +pub use marker::{ + MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, + MarkerValueString, MarkerValueVersion, MarkerWarningKind, StringVersion, +}; +use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; +use puffin_normalize::{ExtraName, PackageName}; + +mod marker; + /// Error with a span attached. Not that those aren't `String` but `Vec` indices. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Pep508Error { @@ -128,10 +132,10 @@ create_exception!( pub struct Requirement { /// The distribution name such as `numpy` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"` - pub name: String, + pub name: PackageName, /// The list of extras such as `security`, `tests` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"` - pub extras: Option>, + pub extras: Option>, /// The version specifier such as `>= 2.8.1`, `== 2.8.*` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"` /// or a url @@ -146,7 +150,15 @@ impl Display for Requirement { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name)?; if let Some(extras) = &self.extras { - write!(f, "[{}]", extras.join(","))?; + write!( + f, + "[{}]", + extras + .iter() + .map(ToString::to_string) + .collect::>() + .join(",") + )?; } if let Some(version_or_url) = &self.version_or_url { match version_or_url { @@ -198,14 +210,16 @@ impl Requirement { /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"` #[getter] pub fn name(&self) -> String { - self.name.clone() + self.name.to_string() } /// The list of extras such as `security`, `tests` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"` #[getter] pub fn extras(&self) -> Option> { - self.extras.clone() + self.extras + .as_ref() + .map(|extras| extras.iter().map(ToString::to_string).collect()) } /// The marker expression such as `python_version > "3.8"` in @@ -511,7 +525,7 @@ impl<'a> CharIter<'a> { } } -fn parse_name(chars: &mut CharIter) -> Result { +fn parse_name(chars: &mut CharIter) -> Result { // https://peps.python.org/pep-0508/#names // ^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$ with re.IGNORECASE let mut name = String::new(); @@ -554,13 +568,13 @@ fn parse_name(chars: &mut CharIter) -> Result { }); } } - Some(_) | None => return Ok(name), + Some(_) | None => return Ok(PackageName::new(name)), } } } /// parses extras in the `[extra1,extra2] format` -fn parse_extras(chars: &mut CharIter) -> Result>, Pep508Error> { +fn parse_extras(chars: &mut CharIter) -> Result>, Pep508Error> { let Some(bracket_pos) = chars.eat('[') else { return Ok(None); }; @@ -627,10 +641,10 @@ fn parse_extras(chars: &mut CharIter) -> Result>, Pep508Error // end or next identifier? match chars.next() { Some((_, ',')) => { - extras.push(buffer); + extras.push(ExtraName::new(buffer)); } Some((_, ']')) => { - extras.push(buffer); + extras.push(ExtraName::new(buffer)); break; } Some((pos, other)) => { @@ -870,15 +884,19 @@ pub fn python_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { /// Half of these tests are copied from #[cfg(test)] mod tests { + use std::str::FromStr; + + use indoc::indoc; + use url::Url; + + use pep440_rs::{Operator, Version, VersionSpecifier}; + use puffin_normalize::{ExtraName, PackageName}; + use crate::marker::{ parse_markers_impl, MarkerExpression, MarkerOperator, MarkerTree, MarkerValue, MarkerValueString, MarkerValueVersion, }; use crate::{CharIter, Requirement, VersionOrUrl}; - use indoc::indoc; - use pep440_rs::{Operator, Version, VersionSpecifier}; - use std::str::FromStr; - use url::Url; fn assert_err(input: &str, error: &str) { assert_eq!(Requirement::from_str(input).unwrap_err().to_string(), error); @@ -926,8 +944,8 @@ mod tests { let requests = Requirement::from_str(input).unwrap(); assert_eq!(input, requests.to_string()); let expected = Requirement { - name: "requests".to_string(), - extras: Some(vec!["security".to_string(), "tests".to_string()]), + name: PackageName::new("requests"), + extras: Some(vec![ExtraName::new("security"), ExtraName::new("tests")]), version_or_url: Some(VersionOrUrl::VersionSpecifier( [ VersionSpecifier::new( @@ -972,25 +990,25 @@ mod tests { #[test] fn parenthesized_single() { let numpy = Requirement::from_str("numpy ( >=1.19 )").unwrap(); - assert_eq!(numpy.name, "numpy"); + assert_eq!(numpy.name.as_ref(), "numpy"); } #[test] fn parenthesized_double() { let numpy = Requirement::from_str("numpy ( >=1.19, <2.0 )").unwrap(); - assert_eq!(numpy.name, "numpy"); + assert_eq!(numpy.name.as_ref(), "numpy"); } #[test] fn versions_single() { let numpy = Requirement::from_str("numpy >=1.19 ").unwrap(); - assert_eq!(numpy.name, "numpy"); + assert_eq!(numpy.name.as_ref(), "numpy"); } #[test] fn versions_double() { let numpy = Requirement::from_str("numpy >=1.19, <2.0 ").unwrap(); - assert_eq!(numpy.name, "numpy"); + assert_eq!(numpy.name.as_ref(), "numpy"); } #[test] @@ -1068,7 +1086,7 @@ mod tests { #[test] fn error_extras1() { let numpy = Requirement::from_str("black[d]").unwrap(); - assert_eq!(numpy.extras, Some(vec!["d".to_string()])); + assert_eq!(numpy.extras, Some(vec![ExtraName::new("d")])); } #[test] @@ -1076,7 +1094,7 @@ mod tests { let numpy = Requirement::from_str("black[d,jupyter]").unwrap(); assert_eq!( numpy.extras, - Some(vec!["d".to_string(), "jupyter".to_string()]) + Some(vec![ExtraName::new("d"), ExtraName::new("jupyter")]) ); } @@ -1123,7 +1141,7 @@ mod tests { .unwrap(); let url = "https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686"; let expected = Requirement { - name: "pip".to_string(), + name: PackageName::new("pip"), extras: None, marker: None, version_or_url: Some(VersionOrUrl::Url(Url::parse(url).unwrap())), diff --git a/crates/puffin-cli/Cargo.toml b/crates/puffin-cli/Cargo.toml index 5cd0ee2c2..2d2f9eece 100644 --- a/crates/puffin-cli/Cargo.toml +++ b/crates/puffin-cli/Cargo.toml @@ -26,6 +26,7 @@ puffin-dispatch = { path = "../puffin-dispatch" } puffin-distribution = { path = "../puffin-distribution" } puffin-installer = { path = "../puffin-installer" } puffin-interpreter = { path = "../puffin-interpreter" } +puffin-normalize = { path = "../puffin-normalize" } puffin-package = { path = "../puffin-package" } puffin-resolver = { path = "../puffin-resolver", features = ["clap"] } puffin-workspace = { path = "../puffin-workspace" } diff --git a/crates/puffin-cli/src/commands/pip_uninstall.rs b/crates/puffin-cli/src/commands/pip_uninstall.rs index 323610b87..79d587676 100644 --- a/crates/puffin-cli/src/commands/pip_uninstall.rs +++ b/crates/puffin-cli/src/commands/pip_uninstall.rs @@ -7,7 +7,6 @@ use tracing::debug; use platform_host::Platform; use puffin_interpreter::Virtualenv; -use puffin_package::package_name::PackageName; use crate::commands::{elapsed, ExitStatus}; use crate::printer::Printer; @@ -43,7 +42,7 @@ pub(crate) async fn pip_uninstall( let packages = { let mut packages = requirements .into_iter() - .map(|requirement| PackageName::normalize(requirement.name)) + .map(|requirement| requirement.name) .collect::>(); packages.sort_unstable(); packages.dedup(); diff --git a/crates/puffin-cli/src/commands/reporters.rs b/crates/puffin-cli/src/commands/reporters.rs index 3623a6c6e..3acc602ab 100644 --- a/crates/puffin-cli/src/commands/reporters.rs +++ b/crates/puffin-cli/src/commands/reporters.rs @@ -3,8 +3,8 @@ use std::time::Duration; use indicatif::{ProgressBar, ProgressStyle}; use puffin_distribution::{CachedDistribution, RemoteDistribution, VersionOrUrl}; -use puffin_package::dist_info_name::DistInfoName; -use puffin_package::package_name::PackageName; +use puffin_normalize::ExtraName; +use puffin_normalize::PackageName; use crate::printer::Printer; @@ -171,7 +171,7 @@ impl puffin_resolver::ResolverReporter for ResolverReporter { fn on_progress( &self, name: &PackageName, - extra: Option<&DistInfoName>, + extra: Option<&ExtraName>, version_or_url: VersionOrUrl, ) { match (extra, version_or_url) { diff --git a/crates/puffin-cli/src/main.rs b/crates/puffin-cli/src/main.rs index 12f19bdda..9cbebae64 100644 --- a/crates/puffin-cli/src/main.rs +++ b/crates/puffin-cli/src/main.rs @@ -4,10 +4,11 @@ use std::process::ExitCode; use clap::{Args, Parser, Subcommand}; use colored::Colorize; use directories::ProjectDirs; -use puffin_package::extra_name::ExtraName; +use url::Url; + +use puffin_normalize::ExtraName; use puffin_resolver::{PreReleaseMode, ResolutionMode}; use requirements::ExtrasSpecification; -use url::Url; use crate::commands::ExitStatus; use crate::index_urls::IndexUrls; diff --git a/crates/puffin-cli/src/requirements.rs b/crates/puffin-cli/src/requirements.rs index 0776f87d0..5d152f25e 100644 --- a/crates/puffin-cli/src/requirements.rs +++ b/crates/puffin-cli/src/requirements.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Result}; use fs_err as fs; use pep508_rs::Requirement; -use puffin_package::extra_name::ExtraName; +use puffin_normalize::ExtraName; use puffin_package::requirements_txt::RequirementsTxt; #[derive(Debug)] @@ -107,7 +107,7 @@ impl RequirementsSpecification { for (name, optional_requirements) in project.optional_dependencies.unwrap_or_default() { - let normalized_name = ExtraName::normalize(name); + let normalized_name = ExtraName::new(name); if extras.contains(&normalized_name) { used_extras.insert(normalized_name); requirements.extend(optional_requirements); diff --git a/crates/puffin-client/Cargo.toml b/crates/puffin-client/Cargo.toml index 5cdc3e6e1..0fe743c50 100644 --- a/crates/puffin-client/Cargo.toml +++ b/crates/puffin-client/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.1" edition = "2021" [dependencies] +puffin-normalize = { path = "../puffin-normalize" } puffin-package = { path = "../puffin-package" } futures = { workspace = true } diff --git a/crates/puffin-client/src/client.rs b/crates/puffin-client/src/client.rs index 0be0b6b3a..5a756a216 100644 --- a/crates/puffin-client/src/client.rs +++ b/crates/puffin-client/src/client.rs @@ -11,7 +11,7 @@ use reqwest_retry::RetryTransientMiddleware; use tracing::trace; use url::Url; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use puffin_package::pypi_types::{File, Metadata21, SimpleJson}; use crate::error::Error; @@ -135,7 +135,7 @@ pub struct RegistryClient { impl RegistryClient { /// Fetch a package from the `PyPI` simple API. - pub async fn simple(&self, package_name: impl AsRef) -> Result { + pub async fn simple(&self, package_name: PackageName) -> Result { if self.no_index { return Err(Error::NoIndex(package_name.as_ref().to_string())); } @@ -143,9 +143,7 @@ impl RegistryClient { for index in std::iter::once(&self.index).chain(self.extra_index.iter()) { // Format the URL for PyPI. let mut url = index.clone(); - url.path_segments_mut() - .unwrap() - .push(PackageName::normalize(&package_name).as_ref()); + url.path_segments_mut().unwrap().push(package_name.as_ref()); url.path_segments_mut().unwrap().push(""); url.set_query(Some("format=application/vnd.pypi.simple.v1+json")); diff --git a/crates/puffin-distribution/Cargo.toml b/crates/puffin-distribution/Cargo.toml index 74c345a60..d1fac6379 100644 --- a/crates/puffin-distribution/Cargo.toml +++ b/crates/puffin-distribution/Cargo.toml @@ -12,6 +12,7 @@ license = { workspace = true } [dependencies] pep440_rs = { path = "../pep440-rs" } puffin-cache = { path = "../puffin-cache" } +puffin-normalize = { path = "../puffin-normalize" } puffin-package = { path = "../puffin-package" } anyhow = { workspace = true } diff --git a/crates/puffin-distribution/src/lib.rs b/crates/puffin-distribution/src/lib.rs index 5ca3e14a3..3c1c8dbab 100644 --- a/crates/puffin-distribution/src/lib.rs +++ b/crates/puffin-distribution/src/lib.rs @@ -7,8 +7,7 @@ use url::Url; use pep440_rs::Version; use puffin_cache::CanonicalUrl; -use puffin_package::dist_info_name::DistInfoName; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use puffin_package::pypi_types::File; /// A built distribution (wheel), which either exists remotely or locally. @@ -117,7 +116,9 @@ impl RemoteDistribution { pub fn id(&self) -> String { match self { Self::Registry(name, version, _) => { - format!("{}-{}", DistInfoName::from(name), version) + // https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-dist-info-directory + // `version` is normalized by its `ToString` impl + format!("{}-{}", PackageName::from(name), version) } Self::Url(_name, url) => puffin_cache::digest(&CanonicalUrl::new(url)), } @@ -169,7 +170,7 @@ impl CachedDistribution { return Ok(None); }; - let name = PackageName::normalize(name); + let name = PackageName::new(name); let version = Version::from_str(version).map_err(|err| anyhow!(err))?; let path = path.to_path_buf(); @@ -248,7 +249,7 @@ impl InstalledDistribution { return Ok(None); }; - let name = PackageName::normalize(name); + let name = PackageName::new(name); let version = Version::from_str(version).map_err(|err| anyhow!(err))?; let path = path.to_path_buf(); @@ -355,7 +356,9 @@ impl<'a> RemoteDistributionRef<'a> { pub fn id(&self) -> String { match self { Self::Registry(name, version, _) => { - format!("{}-{}", DistInfoName::from(*name), version) + // https://packaging.python.org/en/latest/specifications/recording-installed-packages/#the-dist-info-directory + // `version` is normalized by its `ToString` impl + format!("{}-{}", PackageName::from(*name), version) } Self::Url(_name, url) => puffin_cache::digest(&CanonicalUrl::new(url)), } diff --git a/crates/puffin-installer/Cargo.toml b/crates/puffin-installer/Cargo.toml index 294b5b59f..2bb983a68 100644 --- a/crates/puffin-installer/Cargo.toml +++ b/crates/puffin-installer/Cargo.toml @@ -16,6 +16,7 @@ pep508_rs = { path = "../pep508-rs" } puffin-client = { path = "../puffin-client" } puffin-distribution = { path = "../puffin-distribution" } puffin-interpreter = { path = "../puffin-interpreter" } +puffin-normalize = { path = "../puffin-normalize" } puffin-package = { path = "../puffin-package" } distribution-filename = { path = "../distribution-filename" } diff --git a/crates/puffin-installer/src/plan.rs b/crates/puffin-installer/src/plan.rs index e3c047116..2266994a1 100644 --- a/crates/puffin-installer/src/plan.rs +++ b/crates/puffin-installer/src/plan.rs @@ -6,7 +6,6 @@ use tracing::debug; use pep508_rs::{Requirement, VersionOrUrl}; use puffin_distribution::{CachedDistribution, InstalledDistribution}; use puffin_interpreter::Virtualenv; -use puffin_package::package_name::PackageName; use crate::url_index::UrlIndex; use crate::{RegistryIndex, SitePackages}; @@ -55,10 +54,8 @@ impl PartitionedRequirements { let mut extraneous = vec![]; for requirement in requirements { - let package = PackageName::normalize(&requirement.name); - // Filter out already-installed packages. - if let Some(distribution) = site_packages.remove(&package) { + if let Some(distribution) = site_packages.remove(&requirement.name) { if requirement.is_satisfied_by(distribution.version()) { debug!("Requirement already satisfied: {distribution}",); continue; @@ -69,19 +66,21 @@ impl PartitionedRequirements { // Identify any locally-available distributions that satisfy the requirement. match requirement.version_or_url.as_ref() { None | Some(VersionOrUrl::VersionSpecifier(_)) => { - if let Some(distribution) = registry_index.get(&package).filter(|dist| { - let CachedDistribution::Registry(_name, version, _path) = dist else { - return false; - }; - requirement.is_satisfied_by(version) - }) { + if let Some(distribution) = + registry_index.get(&requirement.name).filter(|dist| { + let CachedDistribution::Registry(_name, version, _path) = dist else { + return false; + }; + requirement.is_satisfied_by(version) + }) + { debug!("Requirement already cached: {distribution}"); local.push(distribution.clone()); continue; } } Some(VersionOrUrl::Url(url)) => { - if let Some(distribution) = url_index.get(&package, url) { + if let Some(distribution) = url_index.get(&requirement.name, url) { debug!("Requirement already cached: {distribution}"); local.push(distribution.clone()); continue; diff --git a/crates/puffin-installer/src/registry_index.rs b/crates/puffin-installer/src/registry_index.rs index e1dfdc21c..13be63b6a 100644 --- a/crates/puffin-installer/src/registry_index.rs +++ b/crates/puffin-installer/src/registry_index.rs @@ -4,7 +4,7 @@ use std::path::Path; use anyhow::Result; use puffin_distribution::CachedDistribution; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use crate::cache::{CacheShard, WheelCache}; diff --git a/crates/puffin-installer/src/site_packages.rs b/crates/puffin-installer/src/site_packages.rs index 3faab6857..060614b81 100644 --- a/crates/puffin-installer/src/site_packages.rs +++ b/crates/puffin-installer/src/site_packages.rs @@ -5,7 +5,7 @@ use fs_err as fs; use puffin_distribution::InstalledDistribution; use puffin_interpreter::Virtualenv; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; #[derive(Debug, Default)] pub struct SitePackages(BTreeMap); diff --git a/crates/puffin-installer/src/url_index.rs b/crates/puffin-installer/src/url_index.rs index 159b52ada..5dec87132 100644 --- a/crates/puffin-installer/src/url_index.rs +++ b/crates/puffin-installer/src/url_index.rs @@ -4,9 +4,10 @@ use anyhow::Result; use fxhash::FxHashMap; use url::Url; -use crate::cache::{CacheShard, WheelCache}; use puffin_distribution::{CachedDistribution, RemoteDistributionRef}; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; + +use crate::cache::{CacheShard, WheelCache}; /// A local index of distributions that originate from arbitrary URLs (as opposed to being /// downloaded from a registry, like `PyPI`). diff --git a/crates/puffin-normalize/Cargo.toml b/crates/puffin-normalize/Cargo.toml new file mode 100644 index 000000000..1bcc1945c --- /dev/null +++ b/crates/puffin-normalize/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "puffin-normalize" +version = "0.0.1" +edition = "2021" +description = "Normalization for distribution, package and extra anmes" + +[dependencies] +anyhow = { workspace = true } +once_cell = { workspace = true } +regex = { workspace = true } +serde = { workspace = true, features = ["derive"] } + +[dev-dependencies] +indoc = { version = "2.0.4" } +insta = { version = "1.33.0" } +serde_json = { version = "1.0.107" } +tempfile = { version = "3.8.0" } +test-case = { version = "3.2.1" } diff --git a/crates/puffin-package/src/extra_name.rs b/crates/puffin-normalize/src/extra_name.rs similarity index 69% rename from crates/puffin-package/src/extra_name.rs rename to crates/puffin-normalize/src/extra_name.rs index 18ba89f6d..52715f9e8 100644 --- a/crates/puffin-package/src/extra_name.rs +++ b/crates/puffin-normalize/src/extra_name.rs @@ -6,6 +6,14 @@ use anyhow::{anyhow, Error, Result}; use once_cell::sync::Lazy; use regex::Regex; +/// The normalized name of an extra dependency group. +/// +/// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` +/// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. +/// +/// See: +/// - +/// - #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct ExtraName(String); @@ -19,17 +27,8 @@ static NAME_NORMALIZE: Lazy = Lazy::new(|| Regex::new(r"[-_.]+").unwrap() static NAME_VALIDATE: Lazy = Lazy::new(|| Regex::new(r"(?i)^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$").unwrap()); -/// An extra dependency group name. -/// -/// See: -/// - -/// - impl ExtraName { - /// Create a normalized extra name without validation. - /// - /// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` - /// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. - pub fn normalize(name: impl AsRef) -> Self { + pub fn new(name: impl AsRef) -> Self { // TODO(charlie): Avoid allocating in the common case (when no normalization is required). let mut normalized = NAME_NORMALIZE.replace_all(name.as_ref(), "-").to_string(); normalized.make_ascii_lowercase(); @@ -39,7 +38,7 @@ impl ExtraName { /// Create a validated, normalized extra name. pub fn validate(name: impl AsRef) -> Result { if NAME_VALIDATE.is_match(name.as_ref()) { - Ok(Self::normalize(name)) + Ok(Self::new(name)) } else { Err(anyhow!( "Extra names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters" @@ -68,32 +67,14 @@ mod tests { #[test] fn normalize() { + assert_eq!(ExtraName::new("friendly-bard").as_ref(), "friendly-bard"); + assert_eq!(ExtraName::new("Friendly-Bard").as_ref(), "friendly-bard"); + assert_eq!(ExtraName::new("FRIENDLY-BARD").as_ref(), "friendly-bard"); + assert_eq!(ExtraName::new("friendly.bard").as_ref(), "friendly-bard"); + assert_eq!(ExtraName::new("friendly_bard").as_ref(), "friendly-bard"); + assert_eq!(ExtraName::new("friendly--bard").as_ref(), "friendly-bard"); assert_eq!( - ExtraName::normalize("friendly-bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - ExtraName::normalize("Friendly-Bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - ExtraName::normalize("FRIENDLY-BARD").as_ref(), - "friendly-bard" - ); - assert_eq!( - ExtraName::normalize("friendly.bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - ExtraName::normalize("friendly_bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - ExtraName::normalize("friendly--bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - ExtraName::normalize("FrIeNdLy-._.-bArD").as_ref(), + ExtraName::new("FrIeNdLy-._.-bArD").as_ref(), "friendly-bard" ); } diff --git a/crates/puffin-normalize/src/lib.rs b/crates/puffin-normalize/src/lib.rs new file mode 100644 index 000000000..ed6a73fd9 --- /dev/null +++ b/crates/puffin-normalize/src/lib.rs @@ -0,0 +1,5 @@ +pub use extra_name::ExtraName; +pub use package_name::PackageName; + +mod extra_name; +mod package_name; diff --git a/crates/puffin-normalize/src/package_name.rs b/crates/puffin-normalize/src/package_name.rs new file mode 100644 index 000000000..d569dfb76 --- /dev/null +++ b/crates/puffin-normalize/src/package_name.rs @@ -0,0 +1,74 @@ +use std::fmt; +use std::fmt::{Display, Formatter}; + +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{Deserialize, Deserializer, Serialize}; + +/// The normalized name of a package. +/// +/// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` +/// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. +/// +/// See: +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)] +pub struct PackageName(String); + +impl From<&PackageName> for PackageName { + /// Required for `WaitMap::wait` + fn from(package_name: &PackageName) -> Self { + package_name.clone() + } +} + +impl Display for PackageName { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +static NAME_NORMALIZE: Lazy = Lazy::new(|| Regex::new(r"[-_.]+").unwrap()); + +impl PackageName { + pub fn new(name: impl AsRef) -> Self { + // TODO(charlie): Avoid allocating in the common case (when no normalization is required). + let mut normalized = NAME_NORMALIZE.replace_all(name.as_ref(), "-").to_string(); + normalized.make_ascii_lowercase(); + Self(normalized) + } +} + +impl AsRef for PackageName { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl<'de> Deserialize<'de> for PackageName { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(Self::new(s)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn normalize() { + assert_eq!(PackageName::new("friendly-bard").as_ref(), "friendly-bard"); + assert_eq!(PackageName::new("Friendly-Bard").as_ref(), "friendly-bard"); + assert_eq!(PackageName::new("FRIENDLY-BARD").as_ref(), "friendly-bard"); + assert_eq!(PackageName::new("friendly.bard").as_ref(), "friendly-bard"); + assert_eq!(PackageName::new("friendly_bard").as_ref(), "friendly-bard"); + assert_eq!(PackageName::new("friendly--bard").as_ref(), "friendly-bard"); + assert_eq!( + PackageName::new("FrIeNdLy-._.-bArD").as_ref(), + "friendly-bard" + ); + } +} diff --git a/crates/puffin-package/Cargo.toml b/crates/puffin-package/Cargo.toml index 4b796bb59..443794f71 100644 --- a/crates/puffin-package/Cargo.toml +++ b/crates/puffin-package/Cargo.toml @@ -6,11 +6,11 @@ edition = "2021" [dependencies] pep440_rs = { path = "../pep440-rs", features = ["serde"] } pep508_rs = { path = "../pep508-rs", features = ["serde"] } +puffin-normalize = { path = "../puffin-normalize" } anyhow = { workspace = true } fs-err = { workspace = true } mailparse = { workspace = true } -memchr = { workspace = true } once_cell = { workspace = true } regex = { workspace = true } rfc2047-decoder = { workspace = true } diff --git a/crates/puffin-package/src/dist_info_name.rs b/crates/puffin-package/src/dist_info_name.rs deleted file mode 100644 index f3418ab3e..000000000 --- a/crates/puffin-package/src/dist_info_name.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::fmt; -use std::fmt::{Display, Formatter}; - -use once_cell::sync::Lazy; -use regex::Regex; - -use crate::package_name::PackageName; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct DistInfoName(String); - -impl Display for DistInfoName { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -static NAME_NORMALIZE: Lazy = Lazy::new(|| Regex::new(r"[-_.]+").unwrap()); - -impl DistInfoName { - /// See: - pub fn normalize(name: impl AsRef) -> Self { - // TODO(charlie): Avoid allocating in the common case (when no normalization is required). - let mut normalized = NAME_NORMALIZE.replace_all(name.as_ref(), "_").to_string(); - normalized.make_ascii_lowercase(); - Self(normalized) - } -} - -impl AsRef for DistInfoName { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl From<&PackageName> for DistInfoName { - fn from(package_name: &PackageName) -> Self { - Self::normalize(package_name) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn normalize() { - assert_eq!( - DistInfoName::normalize("friendly-bard").as_ref(), - "friendly_bard" - ); - assert_eq!( - DistInfoName::normalize("Friendly-Bard").as_ref(), - "friendly_bard" - ); - assert_eq!( - DistInfoName::normalize("FRIENDLY-BARD").as_ref(), - "friendly_bard" - ); - assert_eq!( - DistInfoName::normalize("friendly.bard").as_ref(), - "friendly_bard" - ); - assert_eq!( - DistInfoName::normalize("friendly_bard").as_ref(), - "friendly_bard" - ); - assert_eq!( - DistInfoName::normalize("friendly--bard").as_ref(), - "friendly_bard" - ); - assert_eq!( - DistInfoName::normalize("FrIeNdLy-._.-bArD").as_ref(), - "friendly_bard" - ); - } -} diff --git a/crates/puffin-package/src/lib.rs b/crates/puffin-package/src/lib.rs index 58fef082e..399ae6dd0 100644 --- a/crates/puffin-package/src/lib.rs +++ b/crates/puffin-package/src/lib.rs @@ -1,5 +1,2 @@ -pub mod dist_info_name; -pub mod extra_name; -pub mod package_name; pub mod pypi_types; pub mod requirements_txt; diff --git a/crates/puffin-package/src/package_name.rs b/crates/puffin-package/src/package_name.rs deleted file mode 100644 index f13c8e92a..000000000 --- a/crates/puffin-package/src/package_name.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::fmt; -use std::fmt::{Display, Formatter}; - -use once_cell::sync::Lazy; -use regex::Regex; - -use crate::dist_info_name::DistInfoName; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct PackageName(String); - -impl From<&PackageName> for PackageName { - /// Required for `WaitMap::wait` - fn from(package_name: &PackageName) -> Self { - package_name.clone() - } -} - -impl Display for PackageName { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -static NAME_NORMALIZE: Lazy = Lazy::new(|| Regex::new(r"[-_.]+").unwrap()); - -impl PackageName { - /// Create a normalized representation of a package name. - /// - /// Converts the name to lowercase and collapses any run of the characters `-`, `_` and `.` - /// down to a single `-`, e.g., `---`, `.`, and `__` all get converted to just `-`. - /// - /// See: - pub fn normalize(name: impl AsRef) -> Self { - // TODO(charlie): Avoid allocating in the common case (when no normalization is required). - let mut normalized = NAME_NORMALIZE.replace_all(name.as_ref(), "-").to_string(); - normalized.make_ascii_lowercase(); - Self(normalized) - } -} - -impl AsRef for PackageName { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl From for PackageName { - fn from(dist_info_name: DistInfoName) -> Self { - Self::normalize(dist_info_name) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn normalize() { - assert_eq!( - PackageName::normalize("friendly-bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - PackageName::normalize("Friendly-Bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - PackageName::normalize("FRIENDLY-BARD").as_ref(), - "friendly-bard" - ); - assert_eq!( - PackageName::normalize("friendly.bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - PackageName::normalize("friendly_bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - PackageName::normalize("friendly--bard").as_ref(), - "friendly-bard" - ); - assert_eq!( - PackageName::normalize("FrIeNdLy-._.-bArD").as_ref(), - "friendly-bard" - ); - } -} diff --git a/crates/puffin-package/src/pypi_types/metadata.rs b/crates/puffin-package/src/pypi_types/metadata.rs index f697f4e44..3058efaad 100644 --- a/crates/puffin-package/src/pypi_types/metadata.rs +++ b/crates/puffin-package/src/pypi_types/metadata.rs @@ -13,6 +13,7 @@ use tracing::warn; use pep440_rs::{Pep440Error, Version, VersionSpecifiers}; use pep508_rs::{Pep508Error, Requirement}; +use puffin_normalize::PackageName; /// Python Package Metadata 2.1 as specified in /// @@ -24,7 +25,7 @@ use pep508_rs::{Pep508Error, Requirement}; pub struct Metadata21 { // Mandatory fields pub metadata_version: String, - pub name: String, + pub name: PackageName, pub version: Version, // Optional fields pub platforms: Vec, @@ -42,7 +43,7 @@ pub struct Metadata21 { pub license: Option, pub classifiers: Vec, pub requires_dist: Vec, - pub provides_dist: Vec, + pub provides_dist: Vec, pub obsoletes_dist: Vec, pub requires_python: Option, pub requires_external: Vec, @@ -122,9 +123,11 @@ impl Metadata21 { let metadata_version = headers .get_first_value("Metadata-Version") .ok_or(Error::FieldNotFound("Metadata-Version"))?; - let name = headers - .get_first_value("Name") - .ok_or(Error::FieldNotFound("Name"))?; + let name = PackageName::new( + headers + .get_first_value("Name") + .ok_or(Error::FieldNotFound("Name"))?, + ); let version = Version::from_str( &headers .get_first_value("Version") @@ -151,7 +154,10 @@ impl Metadata21 { .iter() .map(|requires_dist| LenientRequirement::from_str(requires_dist).map(Requirement::from)) .collect::, _>>()?; - let provides_dist = get_all_values("Provides-Dist"); + let provides_dist = get_all_values("Provides-Dist") + .iter() + .map(PackageName::new) + .collect(); let obsoletes_dist = get_all_values("Obsoletes-Dist"); let maintainer = get_first_value("Maintainer"); let maintainer_email = get_first_value("Maintainer-email"); diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-basic.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-basic.txt.snap index 034089301..dee1ed81a 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-basic.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-basic.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pandas", + name: PackageName( + "pandas", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -70,7 +74,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "python-dateutil", + name: PackageName( + "python-dateutil", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -102,7 +108,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pytz", + name: PackageName( + "pytz", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -133,7 +141,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "six", + name: PackageName( + "six", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -165,7 +175,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "tzdata", + name: PackageName( + "tzdata", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-a.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-a.txt.snap index 1b37888d0..57be605cf 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-a.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-a.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "django-debug-toolbar", + name: PackageName( + "django-debug-toolbar", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { ], constraints: [ Requirement { - name: "django", + name: PackageName( + "django", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -66,7 +70,9 @@ RequirementsTxt { marker: None, }, Requirement { - name: "pytz", + name: PackageName( + "pytz", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-b.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-b.txt.snap index d83fe87ac..2929abb86 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-b.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-constraints-b.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "django", + name: PackageName( + "django", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pytz", + name: PackageName( + "pytz", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-for-poetry.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-for-poetry.txt.snap index c60fd9e4e..c46ba1147 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-for-poetry.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-for-poetry.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "inflection", + name: PackageName( + "inflection", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "upsidedown", + name: PackageName( + "upsidedown", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -69,7 +73,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: None, marker: None, @@ -79,10 +85,14 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pandas", + name: PackageName( + "pandas", + ), extras: Some( [ - "tabulate", + ExtraName( + "tabulate", + ), ], ), version_or_url: Some( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-a.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-a.txt.snap index c590fb118..041e8c0c2 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-a.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-a.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "tomli", + name: PackageName( + "tomli", + ), extras: None, version_or_url: None, marker: None, @@ -16,7 +18,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-b.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-b.txt.snap index 0c97db984..24bc648b7 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-b.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-include-b.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "tomli", + name: PackageName( + "tomli", + ), extras: None, version_or_url: None, marker: None, diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-poetry-with-hashes.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-poetry-with-hashes.txt.snap index b7c48b752..dedf65566 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-poetry-with-hashes.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-poetry-with-hashes.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "werkzeug", + name: PackageName( + "werkzeug", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -67,7 +69,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "urllib3", + name: PackageName( + "urllib3", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -128,7 +132,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "ansicon", + name: PackageName( + "ansicon", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -200,7 +206,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "requests-oauthlib", + name: PackageName( + "requests-oauthlib", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -262,7 +270,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "psycopg2", + name: PackageName( + "psycopg2", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-small.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-small.txt.snap index 636e5084d..5535453f5 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-small.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-small.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "tqdm", + name: PackageName( + "tqdm", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "tomli-w", + name: PackageName( + "tomli-w", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-whitespace.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-whitespace.txt.snap index 9d81dfa55..2f816172a 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-whitespace.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__line-endings-whitespace.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: None, marker: None, @@ -16,10 +18,14 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pandas", + name: PackageName( + "pandas", + ), extras: Some( [ - "tabulate", + ExtraName( + "tabulate", + ), ], ), version_or_url: Some( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-basic.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-basic.txt.snap index 034089301..dee1ed81a 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-basic.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-basic.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pandas", + name: PackageName( + "pandas", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -70,7 +74,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "python-dateutil", + name: PackageName( + "python-dateutil", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -102,7 +108,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pytz", + name: PackageName( + "pytz", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -133,7 +141,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "six", + name: PackageName( + "six", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -165,7 +175,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "tzdata", + name: PackageName( + "tzdata", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-a.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-a.txt.snap index 1b37888d0..57be605cf 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-a.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-a.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "django-debug-toolbar", + name: PackageName( + "django-debug-toolbar", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { ], constraints: [ Requirement { - name: "django", + name: PackageName( + "django", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -66,7 +70,9 @@ RequirementsTxt { marker: None, }, Requirement { - name: "pytz", + name: PackageName( + "pytz", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-b.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-b.txt.snap index d83fe87ac..2929abb86 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-b.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-constraints-b.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "django", + name: PackageName( + "django", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pytz", + name: PackageName( + "pytz", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-for-poetry.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-for-poetry.txt.snap index c60fd9e4e..c46ba1147 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-for-poetry.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-for-poetry.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "inflection", + name: PackageName( + "inflection", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "upsidedown", + name: PackageName( + "upsidedown", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -69,7 +73,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: None, marker: None, @@ -79,10 +85,14 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pandas", + name: PackageName( + "pandas", + ), extras: Some( [ - "tabulate", + ExtraName( + "tabulate", + ), ], ), version_or_url: Some( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-a.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-a.txt.snap index c590fb118..041e8c0c2 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-a.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-a.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "tomli", + name: PackageName( + "tomli", + ), extras: None, version_or_url: None, marker: None, @@ -16,7 +18,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-b.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-b.txt.snap index 0c97db984..24bc648b7 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-b.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-include-b.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "tomli", + name: PackageName( + "tomli", + ), extras: None, version_or_url: None, marker: None, diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-poetry-with-hashes.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-poetry-with-hashes.txt.snap index b7c48b752..dedf65566 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-poetry-with-hashes.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-poetry-with-hashes.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "werkzeug", + name: PackageName( + "werkzeug", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -67,7 +69,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "urllib3", + name: PackageName( + "urllib3", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -128,7 +132,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "ansicon", + name: PackageName( + "ansicon", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -200,7 +206,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "requests-oauthlib", + name: PackageName( + "requests-oauthlib", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -262,7 +270,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "psycopg2", + name: PackageName( + "psycopg2", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-small.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-small.txt.snap index 636e5084d..5535453f5 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-small.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-small.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "tqdm", + name: PackageName( + "tqdm", + ), extras: None, version_or_url: Some( VersionSpecifier( @@ -38,7 +40,9 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "tomli-w", + name: PackageName( + "tomli-w", + ), extras: None, version_or_url: Some( VersionSpecifier( diff --git a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-whitespace.txt.snap b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-whitespace.txt.snap index 9d81dfa55..2f816172a 100644 --- a/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-whitespace.txt.snap +++ b/crates/puffin-package/src/snapshots/puffin_package__requirements_txt__test__parse-whitespace.txt.snap @@ -6,7 +6,9 @@ RequirementsTxt { requirements: [ RequirementEntry { requirement: Requirement { - name: "numpy", + name: PackageName( + "numpy", + ), extras: None, version_or_url: None, marker: None, @@ -16,10 +18,14 @@ RequirementsTxt { }, RequirementEntry { requirement: Requirement { - name: "pandas", + name: PackageName( + "pandas", + ), extras: Some( [ - "tabulate", + ExtraName( + "tabulate", + ), ], ), version_or_url: Some( diff --git a/crates/puffin-resolver/Cargo.toml b/crates/puffin-resolver/Cargo.toml index d25430020..ef1455686 100644 --- a/crates/puffin-resolver/Cargo.toml +++ b/crates/puffin-resolver/Cargo.toml @@ -18,6 +18,7 @@ platform-tags = { path = "../platform-tags" } pubgrub = { path = "../../vendor/pubgrub" } puffin-client = { path = "../puffin-client" } puffin-distribution = { path = "../puffin-distribution" } +puffin-normalize = { path = "../puffin-normalize" } puffin-package = { path = "../puffin-package" } puffin-traits = { path = "../puffin-traits" } distribution-filename = { path = "../distribution-filename" } diff --git a/crates/puffin-resolver/src/candidate_selector.rs b/crates/puffin-resolver/src/candidate_selector.rs index cb5b60e73..d1ac77295 100644 --- a/crates/puffin-resolver/src/candidate_selector.rs +++ b/crates/puffin-resolver/src/candidate_selector.rs @@ -2,7 +2,7 @@ use fxhash::FxHashMap; use pubgrub::range::Range; use pep508_rs::{Requirement, VersionOrUrl}; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use crate::file::DistributionFile; use crate::prerelease_mode::PreReleaseStrategy; @@ -59,9 +59,8 @@ impl From<&[Requirement]> for Preferences { let [version_specifier] = &**version_specifiers else { return None; }; - let package_name = PackageName::normalize(&requirement.name); let version = PubGrubVersion::from(version_specifier.version().clone()); - Some((package_name, version)) + Some((requirement.name.clone(), version)) }) .collect(), ) diff --git a/crates/puffin-resolver/src/prerelease_mode.rs b/crates/puffin-resolver/src/prerelease_mode.rs index 1217ef333..025e64c60 100644 --- a/crates/puffin-resolver/src/prerelease_mode.rs +++ b/crates/puffin-resolver/src/prerelease_mode.rs @@ -1,7 +1,7 @@ use fxhash::FxHashSet; use pep508_rs::{Requirement, VersionOrUrl}; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] @@ -70,7 +70,7 @@ impl PreReleaseStrategy { .iter() .any(pep440_rs::VersionSpecifier::any_prerelease) }) - .map(|requirement| PackageName::normalize(&requirement.name)) + .map(|requirement| requirement.name.clone()) .collect(), ), PreReleaseMode::IfNecessaryOrExplicit => Self::IfNecessaryOrExplicit( @@ -90,7 +90,7 @@ impl PreReleaseStrategy { .iter() .any(pep440_rs::VersionSpecifier::any_prerelease) }) - .map(|requirement| PackageName::normalize(&requirement.name)) + .map(|requirement| requirement.name.clone()) .collect(), ), } diff --git a/crates/puffin-resolver/src/pubgrub/mod.rs b/crates/puffin-resolver/src/pubgrub/mod.rs index 8a6dfcd6f..313a36a32 100644 --- a/crates/puffin-resolver/src/pubgrub/mod.rs +++ b/crates/puffin-resolver/src/pubgrub/mod.rs @@ -4,8 +4,7 @@ use pubgrub::range::Range; use tracing::warn; use pep508_rs::{MarkerEnvironment, Requirement, VersionOrUrl}; -use puffin_package::dist_info_name::DistInfoName; -use puffin_package::package_name::PackageName; +use puffin_normalize::{ExtraName, PackageName}; pub(crate) use crate::pubgrub::package::PubGrubPackage; pub(crate) use crate::pubgrub::priority::{PubGrubPriorities, PubGrubPriority}; @@ -20,16 +19,15 @@ mod version; /// Convert a set of requirements to a set of `PubGrub` packages and ranges. pub(crate) fn iter_requirements<'a>( requirements: impl Iterator + 'a, - extra: Option<&'a DistInfoName>, + extra: Option<&'a ExtraName>, source: Option<&'a PackageName>, env: &'a MarkerEnvironment, ) -> impl Iterator)> + 'a { requirements .filter(move |requirement| { - let normalized = PackageName::normalize(&requirement.name); - if source.is_some_and(|source| source == &normalized) { + if source.is_some_and(|source| source == &requirement.name) { // TODO(konstin): Warn only once here - warn!("{normalized} depends on itself"); + warn!("{} depends on itself", requirement.name); false } else { true @@ -52,7 +50,7 @@ pub(crate) fn iter_requirements<'a>( .into_iter() .flatten() .map(|extra| { - pubgrub_package(requirement, Some(DistInfoName::normalize(extra))).unwrap() + pubgrub_package(requirement, Some(ExtraName::new(extra))).unwrap() }), ) }) @@ -79,21 +77,17 @@ pub(crate) fn version_range(specifiers: Option<&VersionOrUrl>) -> Result, + extra: Option, ) -> Result<(PubGrubPackage, Range)> { match requirement.version_or_url.as_ref() { // The requirement has no specifier (e.g., `flask`). None => Ok(( - PubGrubPackage::Package(PackageName::normalize(&requirement.name), extra, None), + PubGrubPackage::Package(requirement.name.clone(), extra, None), Range::full(), )), // The requirement has a URL (e.g., `flask @ file:///path/to/flask`). Some(VersionOrUrl::Url(url)) => Ok(( - PubGrubPackage::Package( - PackageName::normalize(&requirement.name), - extra, - Some(url.clone()), - ), + PubGrubPackage::Package(requirement.name.clone(), extra, Some(url.clone())), Range::full(), )), // The requirement has a specifier (e.g., `flask>=1.0`). @@ -105,7 +99,7 @@ fn pubgrub_package( range.intersection(&specifier.into()) })?; Ok(( - PubGrubPackage::Package(PackageName::normalize(&requirement.name), extra, None), + PubGrubPackage::Package(requirement.name.clone(), extra, None), version, )) } diff --git a/crates/puffin-resolver/src/pubgrub/package.rs b/crates/puffin-resolver/src/pubgrub/package.rs index 5f9338700..14f07c62a 100644 --- a/crates/puffin-resolver/src/pubgrub/package.rs +++ b/crates/puffin-resolver/src/pubgrub/package.rs @@ -1,8 +1,7 @@ use derivative::Derivative; use url::Url; -use puffin_package::dist_info_name::DistInfoName; -use puffin_package::package_name::PackageName; +use puffin_normalize::{ExtraName, PackageName}; /// A PubGrub-compatible wrapper around a "Python package", with two notable characteristics: /// @@ -17,7 +16,7 @@ pub enum PubGrubPackage { Root, Package( PackageName, - Option, + Option, #[derivative(PartialEq = "ignore")] #[derivative(PartialOrd = "ignore")] #[derivative(Hash = "ignore")] diff --git a/crates/puffin-resolver/src/pubgrub/priority.rs b/crates/puffin-resolver/src/pubgrub/priority.rs index ce7932a59..2bad201c1 100644 --- a/crates/puffin-resolver/src/pubgrub/priority.rs +++ b/crates/puffin-resolver/src/pubgrub/priority.rs @@ -1,8 +1,11 @@ -use crate::pubgrub::package::PubGrubPackage; -use fxhash::FxHashMap; -use puffin_package::package_name::PackageName; use std::cmp::Reverse; +use fxhash::FxHashMap; + +use puffin_normalize::PackageName; + +use crate::pubgrub::package::PubGrubPackage; + #[derive(Debug, Default)] pub(crate) struct PubGrubPriorities(FxHashMap); diff --git a/crates/puffin-resolver/src/resolution.rs b/crates/puffin-resolver/src/resolution.rs index 0148cebbe..f2073bc55 100644 --- a/crates/puffin-resolver/src/resolution.rs +++ b/crates/puffin-resolver/src/resolution.rs @@ -10,7 +10,7 @@ use pubgrub::type_aliases::SelectedDependencies; use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers}; use pep508_rs::{Requirement, VersionOrUrl}; use puffin_distribution::RemoteDistribution; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use puffin_package::pypi_types::File; use crate::pubgrub::{PubGrubPackage, PubGrubPriority, PubGrubVersion}; @@ -139,7 +139,7 @@ impl Graph { .node_indices() .map(|node| match &self.0[node] { RemoteDistribution::Registry(name, version, _file) => Requirement { - name: name.to_string(), + name: name.clone(), extras: None, version_or_url: Some(VersionOrUrl::VersionSpecifier(VersionSpecifiers::from( VersionSpecifier::equals_version(version.clone()), @@ -147,7 +147,7 @@ impl Graph { marker: None, }, RemoteDistribution::Url(name, url) => Requirement { - name: name.to_string(), + name: name.clone(), extras: None, version_or_url: Some(VersionOrUrl::Url(url.clone())), marker: None, diff --git a/crates/puffin-resolver/src/resolution_mode.rs b/crates/puffin-resolver/src/resolution_mode.rs index 973508875..1b16f86fd 100644 --- a/crates/puffin-resolver/src/resolution_mode.rs +++ b/crates/puffin-resolver/src/resolution_mode.rs @@ -1,7 +1,7 @@ use fxhash::FxHashSet; use pep508_rs::Requirement; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] @@ -37,7 +37,7 @@ impl ResolutionStrategy { ResolutionMode::LowestDirect => Self::LowestDirect( direct_dependencies .iter() - .map(|requirement| PackageName::normalize(&requirement.name)) + .map(|requirement| requirement.name.clone()) .collect(), ), } diff --git a/crates/puffin-resolver/src/resolver.rs b/crates/puffin-resolver/src/resolver.rs index 959e8231e..60e3fa93d 100644 --- a/crates/puffin-resolver/src/resolver.rs +++ b/crates/puffin-resolver/src/resolver.rs @@ -23,8 +23,7 @@ use pep508_rs::{MarkerEnvironment, Requirement}; use platform_tags::Tags; use puffin_client::RegistryClient; use puffin_distribution::{RemoteDistributionRef, VersionOrUrl}; -use puffin_package::dist_info_name::DistInfoName; -use puffin_package::package_name::PackageName; +use puffin_normalize::{ExtraName, PackageName}; use puffin_package::pypi_types::{File, Metadata21, SimpleJson}; use puffin_traits::BuildContext; @@ -126,7 +125,7 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> { // Push all the requirements into the package sink. for requirement in &self.requirements { debug!("Adding root dependency: {requirement}"); - let package_name = PackageName::normalize(&requirement.name); + let package_name = requirement.name.clone(); match &requirement.version_or_url { // If this is a registry-based package, fetch the package metadata. None | Some(pep508_rs::VersionOrUrl::VersionSpecifier(_)) => { @@ -535,11 +534,7 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> { // If any packages were further constrained by the user, add those constraints. for constraint in &self.constraints { - let package = PubGrubPackage::Package( - PackageName::normalize(&constraint.name), - None, - None, - ); + let package = PubGrubPackage::Package(constraint.name.clone(), None, None); if let Some(range) = constraints.get_mut(&package) { *range = range.intersection( &version_range(constraint.version_or_url.as_ref()).unwrap(), @@ -551,7 +546,7 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> { if !metadata .provides_extras .iter() - .any(|provided_extra| DistInfoName::normalize(provided_extra) == *extra) + .any(|provided_extra| ExtraName::new(provided_extra) == *extra) { return Ok(Dependencies::Unknown); } @@ -773,7 +768,7 @@ impl<'a, Context: BuildContext + Sync> Resolver<'a, Context> { pub trait Reporter: Send + Sync { /// Callback to invoke when a dependency is resolved. - fn on_progress(&self, name: &PackageName, extra: Option<&DistInfoName>, version: VersionOrUrl); + fn on_progress(&self, name: &PackageName, extra: Option<&ExtraName>, version: VersionOrUrl); /// Callback to invoke when the resolution is complete. fn on_complete(&self); diff --git a/crates/puffin-resolver/src/wheel_finder.rs b/crates/puffin-resolver/src/wheel_finder.rs index a7307a3be..893dc87d2 100644 --- a/crates/puffin-resolver/src/wheel_finder.rs +++ b/crates/puffin-resolver/src/wheel_finder.rs @@ -16,7 +16,7 @@ use pep508_rs::{Requirement, VersionOrUrl}; use platform_tags::Tags; use puffin_client::RegistryClient; use puffin_distribution::RemoteDistribution; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use puffin_package::pypi_types::{File, Metadata21, SimpleJson}; use crate::error::ResolveError; @@ -85,7 +85,7 @@ impl<'a> WheelFinder<'a> { package_sink.unbounded_send(Request::Package(requirement.clone()))?; } Some(VersionOrUrl::Url(url)) => { - let package_name = PackageName::normalize(&requirement.name); + let package_name = requirement.name.clone(); let package = RemoteDistribution::from_url(package_name.clone(), url.clone()); resolution.insert(package_name, package); } @@ -131,7 +131,7 @@ impl<'a> WheelFinder<'a> { ); let package = RemoteDistribution::from_registry( - PackageName::normalize(&metadata.name), + metadata.name, metadata.version, file, ); @@ -141,7 +141,7 @@ impl<'a> WheelFinder<'a> { } // Add to the resolved set. - let normalized_name = PackageName::normalize(&requirement.name); + let normalized_name = requirement.name.clone(); resolution.insert(normalized_name, package); } } diff --git a/crates/puffin-workspace/Cargo.toml b/crates/puffin-workspace/Cargo.toml index a2526a624..314c10a6a 100644 --- a/crates/puffin-workspace/Cargo.toml +++ b/crates/puffin-workspace/Cargo.toml @@ -12,7 +12,7 @@ license = { workspace = true } [dependencies] pep440_rs = { path = "../pep440-rs" } pep508_rs = { path = "../pep508-rs" } -puffin-package = { path = "../puffin-package" } +puffin-normalize = { path = "../puffin-normalize" } fs-err = { workspace = true } pyproject-toml = { workspace = true } diff --git a/crates/puffin-workspace/src/workspace.rs b/crates/puffin-workspace/src/workspace.rs index b09b82f02..524d5dab7 100644 --- a/crates/puffin-workspace/src/workspace.rs +++ b/crates/puffin-workspace/src/workspace.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use toml_edit::Document; use pep508_rs::Requirement; -use puffin_package::package_name::PackageName; +use puffin_normalize::PackageName; use crate::toml::format_multiline_array; use crate::verbatim::VerbatimRequirement; @@ -85,8 +85,7 @@ impl Workspace { return false; }; - PackageName::normalize(&requirement.requirement.name) - == PackageName::normalize(existing.name) + requirement.requirement.name == existing.name }); if let Some(index) = index { @@ -124,7 +123,7 @@ impl Workspace { return false; }; - PackageName::normalize(name) == PackageName::normalize(existing.name) + PackageName::new(name) == existing.name }); let Some(index) = index else {