crates_index/
names.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/// An iterator over all possible permutations of hyphens (`-`) and underscores (`_`) of a crate name.
///
/// The sequence yields the input name first, then an all-hyphens variant of it followed by an
/// all-underscores variant to maximize the chance of finding a match. Then follow all remaining permutations.
///
/// For instance, the name `parking_lot` is turned into the sequence `parking_lot` and `parking-lot`, while
/// `serde-yaml` is turned into `serde-yaml` and `serde_yaml`.
/// Finally, `a-b_c`  is returned as `a-b_c`, `a-b-c`, `a_b_c`, `a_b-c`.
#[derive(Clone)]
pub struct Names {
    count: Option<u16>,
    initial: String,
    max_count: u16,
    current: String,
    separator_indexes: [usize; 17],
    separator_count: usize,
}

impl Names {
    /// Creates a new iterator over all permutations of `-` and `_` of the given `name`,
    /// or `None` if there are more than 15 `-` or `_` characters.
    pub fn new(name: impl Into<String>) -> Option<Names> {
        let mut separator_indexes = [0; 17];
        let mut separator_count = 0;

        let name = name.into();
        let current: String = name
            .chars()
            .enumerate()
            .map(|(index, char)| {
                if char == '-' || char == '_' {
                    separator_indexes[separator_count] = index;
                    separator_count += 1;
                    '_'
                } else {
                    char
                }
            })
            .collect();

        Some(Names {
            count: None,
            initial: name,
            max_count: 2u16.checked_pow(separator_count.try_into().ok()?)?,
            current,
            separator_indexes,
            separator_count,
        })
    }
}

impl Iterator for Names {
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        match self.count.as_mut() {
            None => {
                self.count = Some(0);
                self.initial.clone().into()
            }
            Some(count) => {
                for _round in 0..2 {
                    if *count == self.max_count {
                        return None;
                    }

                    //map the count so the first value is the last one (all "-"), the second one is the first one (all "_")...
                    let used_count = *count as isize - 1 + self.max_count as isize;
                    for (sep_index, char_index) in self.separator_indexes[..self.separator_count].iter().enumerate() {
                        let char = if used_count & (1 << sep_index) == 0 { b'_' } else { b'-' };
                        // SAFETY: We validated that `char_index` is a valid UTF-8 codepoint
                        #[allow(unsafe_code)]
                        unsafe {
                            self.current.as_bytes_mut()[*char_index] = char;
                        }
                    }

                    *count += 1;
                    if self.current != self.initial {
                        break;
                    }
                }
                Some(self.current.clone())
            }
        }
    }

    fn count(self) -> usize
    where
        Self: Sized,
    {
        self.max_count as usize
    }
}