uv_normalize/
dist_info_name.rs1use std::borrow::Cow;
2use std::fmt;
3use std::fmt::{Display, Formatter};
4
5#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct DistInfoName<'a>(Cow<'a, str>);
13
14impl<'a> DistInfoName<'a> {
15 pub fn new(name: &'a str) -> Self {
17 if Self::is_normalized(name) {
18 Self(Cow::Borrowed(name))
19 } else {
20 Self(Cow::Owned(Self::normalize(name)))
21 }
22 }
23
24 fn normalize(name: impl AsRef<str>) -> String {
27 let mut normalized = String::with_capacity(name.as_ref().len());
28 let mut last = None;
29 for char in name.as_ref().bytes() {
30 match char {
31 b'A'..=b'Z' => {
32 normalized.push(char.to_ascii_lowercase() as char);
33 }
34 b'-' | b'_' | b'.' => {
35 if matches!(last, Some(b'-' | b'_' | b'.')) {
36 continue;
37 }
38 normalized.push('-');
39 }
40 _ => {
41 normalized.push(char as char);
42 }
43 }
44 last = Some(char);
45 }
46 normalized
47 }
48
49 fn is_normalized(name: impl AsRef<str>) -> bool {
51 let mut last = None;
52 for char in name.as_ref().bytes() {
53 match char {
54 b'A'..=b'Z' => {
55 return false;
57 }
58 b'_' | b'.' => {
59 return false;
61 }
62 b'-' => {
63 if matches!(last, Some(b'-')) {
64 return false;
66 }
67 }
68 _ => {}
69 }
70 last = Some(char);
71 }
72 true
73 }
74}
75
76impl Display for DistInfoName<'_> {
77 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78 self.0.fmt(f)
79 }
80}
81
82impl AsRef<str> for DistInfoName<'_> {
83 fn as_ref(&self) -> &str {
84 &self.0
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn normalize() {
94 let inputs = [
95 "friendly-bard",
96 "Friendly-Bard",
97 "FRIENDLY-BARD",
98 "friendly.bard",
99 "friendly_bard",
100 "friendly--bard",
101 "friendly-.bard",
102 "FrIeNdLy-._.-bArD",
103 ];
104 for input in inputs {
105 assert_eq!(DistInfoName::normalize(input), "friendly-bard");
106 }
107 }
108}