cleanup package name normalization; PY-79870

(cherry picked from commit cfc8e220df5d7a2a33b2c027bbf37a7d5055ffc5)

IJ-MR-158370

GitOrigin-RevId: 85f665ae8f601052eb21526f974f196d6f53ceb9
This commit is contained in:
Aleksandr Sorotskii
2025-03-25 20:32:06 +01:00
committed by intellij-monorepo-bot
parent d6c51ef1c5
commit 5dde5518fa
3 changed files with 31 additions and 30 deletions

View File

@@ -50,6 +50,7 @@ public interface PyRequirement {
@Nullable
PyPackage match(@NotNull Collection<? extends PyPackage> packages);
boolean match(@NotNull PyPackage packageName);
default boolean isEditable() {
if (getInstallOptions().isEmpty()) return false;

View File

@@ -1,13 +1,17 @@
package com.jetbrains.python.packaging
private val QUOTES_REGEX: Regex = Regex("^\"|\"$")
private val SEPARATOR_REGEX: Regex = Regex("[-_.]+")
fun normalizePackageName(packageName: String): String {
var name = packageName.trim()
.removePrefix("\"")
.removeSuffix("\"")
/**
* Normalizes a package name by removing quotes, replacing separators, and converting to lowercase.
*/
fun normalizePackageName(name: String): String = name
.replace(QUOTES_REGEX, "")
.replace(SEPARATOR_REGEX, "-")
.lowercase()
// e.g. __future__
if (!name.startsWith("_")) {
name = name.replace('_', '-')
}
return name
.replace('.', '-')
.lowercase()
}

View File

@@ -14,45 +14,41 @@ import com.jetbrains.python.packaging.requirement.PyRequirementVersionSpec
* @see PyRequirementParser.fromLine
* @see PyRequirementParser.fromFile
*/
data class PyRequirementImpl(
private val name: String,
class PyRequirementImpl(
private val presentableName: String,
private val versionSpecs: List<PyRequirementVersionSpec>,
private val installOptions: List<String>,
private val extras: String,
) : PyRequirement {
override fun getName(): String = NormalizedPackageName.from(name).name
private val name: String = normalizePackageName(presentableName)
override fun getName(): String = name
override fun getExtras(): String = extras
override fun getVersionSpecs(): List<PyRequirementVersionSpec> = versionSpecs
override fun getInstallOptions(): List<String> = installOptions
override fun getPresentableTextWithoutVersion(): @NlsSafe String = name
override fun getPresentableTextWithoutVersion(): @NlsSafe String = presentableName
override fun match(packages: Collection<PyPackage>): PyPackage? {
return packages.firstOrNull { pkg ->
isPackageNameEqual(pkg.name) && versionSpecs.all { it.matches(pkg.version) }
}
return packages.firstOrNull { this.match(it) }
}
private fun isPackageNameEqual(otherName: String): Boolean {
return normalizePackageName(name) == normalizePackageName(otherName)
override fun match(packageName: PyPackage): Boolean {
return name == normalizePackageName(packageName.name) && versionSpecs
.all { it.matches(packageName.version) }
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (this === other) {
return true
}
return when (other) {
is String -> getName() == NormalizedPackageName.from(other).name
is PyRequirementImpl -> getName().equals(other.getName(), ignoreCase = true)
is String -> name == normalizePackageName(other)
is PyRequirementImpl -> name == other.name // TODO: should we match specs & options ?
else -> false
}
}
override fun hashCode(): Int = normalizePackageName(name).hashCode()
@JvmInline
value class NormalizedPackageName private constructor(val name: String) {
companion object {
fun from(name: String): NormalizedPackageName =
NormalizedPackageName(normalizePackageName(name))
}
}
override fun hashCode(): Int = name.hashCode()
}