pub(crate) use keyhog_core::ascii_ci::ends_with_ignore_ascii_case;
#[inline]
pub(crate) fn starts_with_ignore_ascii_case(bytes: &[u8], prefix: &[u8]) -> bool {
bytes
.get(..prefix.len())
.is_some_and(|p| p.eq_ignore_ascii_case(prefix))
}
#[inline]
pub(crate) fn ci_find(haystack: &[u8], needle_lower: &[u8]) -> bool {
if needle_lower.is_empty() {
return true;
}
ci_find_nonempty(haystack, needle_lower)
}
#[inline]
pub(crate) fn ci_find_nonempty(haystack: &[u8], needle: &[u8]) -> bool {
ci_find_at(haystack, needle).is_some()
}
#[inline]
pub(crate) fn ci_find_at(haystack: &[u8], needle: &[u8]) -> Option<usize> {
let n = needle.len();
if n == 0 || haystack.len() < n {
return None;
}
ci_find_iter(haystack, needle).next()
}
#[inline]
fn rarest_byte_index(needle: &[u8]) -> usize {
let mut best = 0usize;
let mut best_rank = u16::MAX;
let mut i = 0usize;
while i < needle.len() {
let rank = ascii_ci_frequency_rank(needle[i]);
if rank < best_rank {
best_rank = rank;
best = i;
}
i += 1;
}
best
}
#[inline]
const fn ascii_ci_frequency_rank(b: u8) -> u16 {
match b.to_ascii_lowercase() {
b' ' => 255,
b'e' => 200,
b't' => 190,
b'a' => 180,
b'o' => 175,
b'i' => 170,
b'n' => 165,
b's' => 160,
b'r' => 155,
b'h' => 150,
b'l' => 145,
b'd' => 140,
b'c' => 135,
b'u' => 130,
b'm' => 125,
b'f' => 120,
b'p' => 115,
b'g' => 110,
b'w' => 105,
b'y' => 100,
b'b' => 95,
b'v' => 90,
b'k' => 60,
b'x' => 45,
b'j' => 40,
b'q' => 35,
b'z' => 30,
b'0'..=b'9' => 50,
_ => 10,
}
}
#[inline]
pub(crate) fn ci_find_iter<'h, 'n>(haystack: &'h [u8], needle: &'n [u8]) -> CiMatches<'h, 'n> {
let anchor = if needle.is_empty() {
0
} else {
rarest_byte_index(needle)
};
let (a_lower, a_upper) = needle
.get(anchor)
.map(|&b| (b.to_ascii_lowercase(), b.to_ascii_uppercase()))
.map_or((0, 0), |pair| pair);
CiMatches {
haystack,
needle,
anchor,
a_lower,
a_upper,
pos: 0,
}
}
pub(crate) struct CiMatches<'h, 'n> {
haystack: &'h [u8],
needle: &'n [u8],
anchor: usize,
a_lower: u8,
a_upper: u8,
pos: usize,
}
impl Iterator for CiMatches<'_, '_> {
type Item = usize;
#[inline]
fn next(&mut self) -> Option<usize> {
let n = self.needle.len();
if n == 0 || self.haystack.len() < n {
return None;
}
while self.pos <= self.haystack.len() {
let rel = memchr::memchr2(self.a_lower, self.a_upper, &self.haystack[self.pos..])?;
let hit = self.pos + rel;
self.pos = hit + 1;
let Some(start) = hit.checked_sub(self.anchor) else {
continue;
};
if start + n > self.haystack.len() {
return None;
}
if self.haystack[start..start + n].eq_ignore_ascii_case(self.needle) {
return Some(start);
}
}
None
}
}
#[inline]
pub(crate) fn contains_path_segment(path: &str, segment: &str) -> bool {
let bytes = path.as_bytes();
let seg = segment.as_bytes();
let n = seg.len();
if n == 0 || bytes.len() < n {
return false;
}
if bytes[..n].eq_ignore_ascii_case(seg)
&& (bytes.len() == n || matches!(bytes[n], b'/' | b'\\'))
{
return true;
}
for sep_idx in memchr::memchr2_iter(b'/', b'\\', bytes) {
let body_start = sep_idx + 1;
let body_end = body_start + n;
if body_end > bytes.len() {
break;
}
if !bytes[body_start..body_end].eq_ignore_ascii_case(seg) {
continue;
}
if body_end == bytes.len() || matches!(bytes[body_end], b'/' | b'\\') {
return true;
}
}
false
}
#[inline]
pub(crate) fn contains_path_segment_two(path: &str, a: &str, b: &str) -> bool {
let bytes = path.as_bytes();
let a_b = a.as_bytes();
let b_b = b.as_bytes();
if a_b.is_empty() || b_b.is_empty() {
return false;
}
let total = a_b.len() + b_b.len();
if bytes.len() < total + 2 {
return false;
}
{
let a_end = a_b.len();
let b_start = a_end + 1;
let b_end = b_start + b_b.len();
if b_end < bytes.len()
&& bytes[..a_end].eq_ignore_ascii_case(a_b)
&& matches!(bytes[a_end], b'/' | b'\\')
&& bytes[b_start..b_end].eq_ignore_ascii_case(b_b)
&& matches!(bytes[b_end], b'/' | b'\\')
{
return true;
}
}
for sep_idx in memchr::memchr2_iter(b'/', b'\\', bytes) {
let a_start = sep_idx + 1;
let a_end = a_start + a_b.len();
if a_end + 1 + b_b.len() >= bytes.len() {
break;
}
if !bytes[a_start..a_end].eq_ignore_ascii_case(a_b) {
continue;
}
if !matches!(bytes[a_end], b'/' | b'\\') {
continue;
}
let b_start = a_end + 1;
let b_end = b_start + b_b.len();
if b_end >= bytes.len() {
continue;
}
if !bytes[b_start..b_end].eq_ignore_ascii_case(b_b) {
continue;
}
if matches!(bytes[b_end], b'/' | b'\\') {
return true;
}
}
false
}
thread_local! {
static UPPER_SCRATCH: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
}
pub(crate) struct AsciiUpperScratch(String);
impl AsciiUpperScratch {
#[inline]
pub(crate) fn as_str(&self) -> &str {
&self.0
}
}
impl Drop for AsciiUpperScratch {
#[inline]
fn drop(&mut self) {
UPPER_SCRATCH.with(|cell| *cell.borrow_mut() = std::mem::take(&mut self.0));
}
}
#[inline]
pub(crate) fn ascii_upper_scratch(input: &str) -> AsciiUpperScratch {
let mut buf = UPPER_SCRATCH.with(|cell| std::mem::take(&mut *cell.borrow_mut()));
buf.clear();
buf.push_str(input);
buf.make_ascii_uppercase();
AsciiUpperScratch(buf)
}