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
}
}