use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use uv_normalize::PackageName;
use crate::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DistInfoStem<'a>(Cow<'a, str>);
impl<'a> DistInfoStem<'a> {
pub(crate) fn new(
stem: impl Into<Cow<'a, str>>,
package_name: &PackageName,
) -> Result<Self, Error> {
let stem = stem.into();
if !normalize(&stem).starts_with(package_name.as_str()) {
return Err(Error::MissingDistInfoPackageName(
stem.into_owned(),
package_name.to_string(),
));
}
Ok(Self(stem))
}
#[cfg(test)]
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl Display for DistInfoStem<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
fn normalize(stem: &str) -> Cow<'_, str> {
let mut last_was_separator = false;
let normalized = stem.bytes().filter_map(move |byte| {
let byte = match byte {
b'-' | b'_' | b'.' => b'-',
byte => byte.to_ascii_lowercase(),
};
let is_separator = byte == b'-';
let repeated_separator = last_was_separator && is_separator;
last_was_separator = is_separator;
(!repeated_separator).then_some(byte)
});
if normalized.clone().eq(stem.bytes()) {
return Cow::Borrowed(stem);
}
let mut output = String::with_capacity(stem.len());
output.extend(normalized.map(char::from));
Cow::Owned(output)
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
#[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!(super::normalize(input), "friendly-bard");
}
}
#[test]
fn normalize_borrowed() {
for input in ["", "-", "friendly-bard", "friendly+local!", "café"] {
assert_eq!(
match super::normalize(input) {
Cow::Borrowed(normalized) => Some(normalized),
Cow::Owned(_) => None,
},
Some(input)
);
}
}
}