crates_index/names.rs
1/// An iterator over all possible permutations of hyphens (`-`) and underscores (`_`) of a crate name.
2///
3/// The sequence yields the input name first, then an all-hyphens variant of it followed by an
4/// all-underscores variant to maximize the chance of finding a match. Then follow all remaining permutations.
5///
6/// For instance, the name `parking_lot` is turned into the sequence `parking_lot` and `parking-lot`, while
7/// `serde-yaml` is turned into `serde-yaml` and `serde_yaml`.
8/// Finally, `a-b_c` is returned as `a-b_c`, `a-b-c`, `a_b_c`, `a_b-c`.
9#[derive(Clone)]
10pub struct Names {
11 count: Option<u16>,
12 initial: String,
13 max_count: u16,
14 current: String,
15 separator_indexes: [usize; 17],
16 separator_count: usize,
17}
18
19impl Names {
20 /// Creates a new iterator over all permutations of `-` and `_` of the given `name`,
21 /// or `None` if there are more than 15 `-` or `_` characters.
22 pub fn new(name: impl Into<String>) -> Option<Names> {
23 let mut separator_indexes = [0; 17];
24 let mut separator_count = 0;
25
26 let name = name.into();
27 let current: String = name
28 .chars()
29 .enumerate()
30 .map(|(index, char)| {
31 if char == '-' || char == '_' {
32 separator_indexes[separator_count] = index;
33 separator_count += 1;
34 '_'
35 } else {
36 char
37 }
38 })
39 .collect();
40
41 Some(Names {
42 count: None,
43 initial: name,
44 max_count: 2u16.checked_pow(separator_count.try_into().ok()?)?,
45 current,
46 separator_indexes,
47 separator_count,
48 })
49 }
50}
51
52impl Iterator for Names {
53 type Item = String;
54
55 fn next(&mut self) -> Option<Self::Item> {
56 match self.count.as_mut() {
57 None => {
58 self.count = Some(0);
59 self.initial.clone().into()
60 }
61 Some(count) => {
62 for _round in 0..2 {
63 if *count == self.max_count {
64 return None;
65 }
66
67 //map the count so the first value is the last one (all "-"), the second one is the first one (all "_")...
68 let used_count = *count as isize - 1 + self.max_count as isize;
69 for (sep_index, char_index) in self.separator_indexes[..self.separator_count].iter().enumerate() {
70 let char = if used_count & (1 << sep_index) == 0 { b'_' } else { b'-' };
71 // SAFETY: We validated that `char_index` is a valid UTF-8 codepoint
72 #[allow(unsafe_code)]
73 unsafe {
74 self.current.as_bytes_mut()[*char_index] = char;
75 }
76 }
77
78 *count += 1;
79 if self.current != self.initial {
80 break;
81 }
82 }
83 Some(self.current.clone())
84 }
85 }
86 }
87
88 fn count(self) -> usize
89 where
90 Self: Sized,
91 {
92 self.max_count as usize
93 }
94}