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}