1pub fn pypi_normalize(name: &str) -> String {
4 let mut result = String::with_capacity(name.len());
5 let mut in_separator = false;
6
7 for c in name.chars() {
8 if c == '-' || c == '_' || c == '.' {
9 if !in_separator {
10 result.push('-');
11 in_separator = true;
12 }
13 } else {
15 in_separator = false;
16 for lc in c.to_lowercase() {
18 result.push(lc);
19 }
20 }
21 }
22
23 result
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29
30 #[test]
31 fn test_pypi_normalize_basic() {
32 assert_eq!(pypi_normalize("My_Package"), "my-package");
33 assert_eq!(pypi_normalize("some.thing"), "some-thing");
34 assert_eq!(pypi_normalize("UPPER-CASE"), "upper-case");
35 assert_eq!(pypi_normalize("already-normalized"), "already-normalized");
36 assert_eq!(pypi_normalize("CamelCase"), "camelcase");
37 }
38
39 #[test]
40 fn test_pypi_normalize_consecutive_separators() {
41 assert_eq!(
42 pypi_normalize("multiple___underscores"),
43 "multiple-underscores"
44 );
45 assert_eq!(pypi_normalize("dots...and___mixed"), "dots-and-mixed");
46 assert_eq!(pypi_normalize("a_b.c-d"), "a-b-c-d");
47 assert_eq!(pypi_normalize("a-_.-b"), "a-b");
48 }
49
50 #[test]
51 fn test_pypi_normalize_edge_cases() {
52 assert_eq!(pypi_normalize("a"), "a");
53 assert_eq!(pypi_normalize(""), "");
54 assert_eq!(pypi_normalize("_leading"), "-leading");
55 assert_eq!(pypi_normalize("trailing_"), "trailing-");
56 }
57}