mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Add PackageName::validate
This commit is contained in:
parent
3d5f8249ef
commit
ac906c6dc7
1 changed files with 45 additions and 5 deletions
|
@ -1,11 +1,11 @@
|
|||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use crate::dist_info_name::DistInfoName;
|
||||
use anyhow::{anyhow, Result};
|
||||
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);
|
||||
|
||||
|
@ -23,20 +23,37 @@ impl Display for PackageName {
|
|||
}
|
||||
|
||||
static NAME_NORMALIZE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[-_.]+").unwrap());
|
||||
static NAME_VALIDATE: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"(?i)^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$").unwrap());
|
||||
|
||||
/// A package name.
|
||||
///
|
||||
/// See:
|
||||
/// - <https://packaging.python.org/en/latest/specifications/name-normalization/>
|
||||
/// - <https://peps.python.org/pep-0508/#names>
|
||||
/// - <https://peps.python.org/pep-0503/#normalized-names>
|
||||
impl PackageName {
|
||||
/// Create a normalized representation of a package name.
|
||||
/// Create a normalized package 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 `-`.
|
||||
///
|
||||
/// See: <https://packaging.python.org/en/latest/specifications/name-normalization/>
|
||||
pub fn normalize(name: impl AsRef<str>) -> 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)
|
||||
}
|
||||
|
||||
/// Create a validated, normalized extra name.
|
||||
pub fn validate(name: impl AsRef<str>) -> Result<Self> {
|
||||
if NAME_VALIDATE.is_match(name.as_ref()) {
|
||||
Ok(Self::normalize(name))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"Package names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for PackageName {
|
||||
|
@ -86,4 +103,27 @@ mod tests {
|
|||
"friendly-bard"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate() {
|
||||
// Unchanged
|
||||
assert_eq!(
|
||||
PackageName::validate("friendly-bard").unwrap().as_ref(),
|
||||
"friendly-bard"
|
||||
);
|
||||
assert_eq!(PackageName::validate("1okay").unwrap().as_ref(), "1okay");
|
||||
assert_eq!(PackageName::validate("okay2").unwrap().as_ref(), "okay2");
|
||||
// Normalizes
|
||||
assert_eq!(
|
||||
PackageName::validate("Friendly-Bard").unwrap().as_ref(),
|
||||
"friendly-bard"
|
||||
);
|
||||
// Failures...
|
||||
assert!(PackageName::validate(" starts-with-space").is_err());
|
||||
assert!(PackageName::validate("-starts-with-dash").is_err());
|
||||
assert!(PackageName::validate("ends-with-dash-").is_err());
|
||||
assert!(PackageName::validate("ends-with-space ").is_err());
|
||||
assert!(PackageName::validate("includes!invalid-char").is_err());
|
||||
assert!(PackageName::validate("space in middle").is_err());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue