Files
uv/crates/uv-normalize/src/lib.rs
T
Zanie Blue 2586f655bb Rename to uv (#1302)
First, replace all usages in files in-place. I used my editor for this.
If someone wants to add a one-liner that'd be fun.

Then, update directory and file names:

```
# Run twice for nested directories
find . -type d -print0 | xargs -0 rename s/puffin/uv/g
find . -type d -print0 | xargs -0 rename s/puffin/uv/g

# Update files
find . -type f -print0 | xargs -0 rename s/puffin/uv/g
```

Then add all the files again

```
# Add all the files again
git add crates
git add python/uv

# This one needs a force-add
git add -f crates/uv-trampoline
```
2024-02-15 11:19:46 -06:00

186 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::error::Error;
use std::fmt::{Display, Formatter};
pub use extra_name::ExtraName;
pub use package_name::PackageName;
mod extra_name;
mod package_name;
/// Validate and normalize an owned package or extra name.
pub(crate) fn validate_and_normalize_owned(name: String) -> Result<String, InvalidNameError> {
if is_normalized(&name)? {
Ok(name)
} else {
validate_and_normalize_ref(name)
}
}
/// Validate and normalize an unowned package or extra name.
pub(crate) fn validate_and_normalize_ref(
name: impl AsRef<str>,
) -> Result<String, InvalidNameError> {
let mut normalized = String::with_capacity(name.as_ref().len());
let mut last = None;
for char in name.as_ref().bytes() {
match char {
b'A'..=b'Z' => {
normalized.push(char.to_ascii_lowercase() as char);
}
b'a'..=b'z' | b'0'..=b'9' => {
normalized.push(char as char);
}
b'-' | b'_' | b'.' => {
match last {
// Names can't start with punctuation.
None => return Err(InvalidNameError(name.as_ref().to_string())),
Some(b'-') | Some(b'_') | Some(b'.') => {}
Some(_) => normalized.push('-'),
}
}
_ => return Err(InvalidNameError(name.as_ref().to_string())),
}
last = Some(char);
}
// Names can't end with punctuation.
if matches!(last, Some(b'-') | Some(b'_') | Some(b'.')) {
return Err(InvalidNameError(name.as_ref().to_string()));
}
Ok(normalized)
}
/// Returns `true` if the name is already normalized.
fn is_normalized(name: impl AsRef<str>) -> Result<bool, InvalidNameError> {
let mut last = None;
for char in name.as_ref().bytes() {
match char {
b'A'..=b'Z' => {
// Uppercase characters need to be converted to lowercase.
return Ok(false);
}
b'a'..=b'z' | b'0'..=b'9' => {}
b'_' | b'.' => {
// `_` and `.` are normalized to `-`.
return Ok(false);
}
b'-' => {
match last {
// Names can't start with punctuation.
None => return Err(InvalidNameError(name.as_ref().to_string())),
Some(b'-') => {
// Runs of `-` are normalized to a single `-`.
return Ok(false);
}
Some(_) => {}
}
}
_ => return Err(InvalidNameError(name.as_ref().to_string())),
}
last = Some(char);
}
// Names can't end with punctuation.
if matches!(last, Some(b'-') | Some(b'_') | Some(b'.')) {
return Err(InvalidNameError(name.as_ref().to_string()));
}
Ok(true)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InvalidNameError(String);
impl Display for InvalidNameError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Not a valid package or extra name: \"{}\". Names must start and end with a letter or \
digit and may only contain -, _, ., and alphanumeric characters.",
self.0
)
}
}
impl Error for InvalidNameError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalize() {
let inputs = [
"friendly-bard",
"Friendly-Bard",
"FRIENDLY-BARD",
"friendly.bard",
"friendly_bard",
"friendly--bard",
"friendly-.bard",
"FrIeNdLy-._.-bArD",
];
for input in inputs {
assert_eq!(validate_and_normalize_ref(input).unwrap(), "friendly-bard");
assert_eq!(
validate_and_normalize_owned(input.to_string()).unwrap(),
"friendly-bard"
);
}
}
#[test]
fn check() {
let inputs = ["friendly-bard", "friendlybard"];
for input in inputs {
assert!(is_normalized(input).unwrap(), "{:?}", input);
}
let inputs = [
"friendly.bard",
"friendly.BARD",
"friendly_bard",
"friendly--bard",
"friendly-.bard",
"FrIeNdLy-._.-bArD",
];
for input in inputs {
assert!(!is_normalized(input).unwrap(), "{:?}", input);
}
}
#[test]
fn unchanged() {
// Unchanged
let unchanged = ["friendly-bard", "1okay", "okay2"];
for input in unchanged {
assert_eq!(validate_and_normalize_ref(input).unwrap(), input);
assert_eq!(
validate_and_normalize_owned(input.to_string()).unwrap(),
input
);
assert!(is_normalized(input).unwrap());
}
}
#[test]
fn failures() {
let failures = [
" starts-with-space",
"-starts-with-dash",
"ends-with-dash-",
"ends-with-space ",
"includes!invalid-char",
"space in middle",
"alpha-α",
];
for input in failures {
assert!(validate_and_normalize_ref(input).is_err());
assert!(validate_and_normalize_owned(input.to_string()).is_err());
assert!(is_normalized(input).is_err());
}
}
}