use std::borrow::Cow;
use std::fmt;
use std::fmt::{Display, Formatter};
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DistInfoName<'a>(Cow<'a, str>);
impl<'a> DistInfoName<'a> {
pub fn new(name: &'a str) -> Self {
if Self::is_normalized(name) {
Self(Cow::Borrowed(name))
} else {
Self(Cow::Owned(Self::normalize(name)))
}
}
fn normalize(name: impl AsRef<str>) -> String {
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'-' | b'_' | b'.' => {
if matches!(last, Some(b'-' | b'_' | b'.')) {
continue;
}
normalized.push('-');
}
_ => {
normalized.push(char as char);
}
}
last = Some(char);
}
normalized
}
fn is_normalized(name: impl AsRef<str>) -> bool {
let mut last = None;
for char in name.as_ref().bytes() {
match char {
b'A'..=b'Z' => {
return false;
}
b'_' | b'.' => {
return false;
}
b'-' => {
if matches!(last, Some(b'-')) {
return false;
}
}
_ => {}
}
last = Some(char);
}
true
}
}
impl Display for DistInfoName<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for DistInfoName<'_> {
fn as_ref(&self) -> &str {
&self.0
}
}
#[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!(DistInfoName::normalize(input), "friendly-bard");
}
}
}