use crate::Range;
use fancy_regex::Regex;
pub(crate) fn replacements_contain_dashless<T: AsRef<str>>(word: &str, replacements: &[T]) -> bool {
let dashless = word.chars().filter(|c| *c != '-').collect::<String>();
if dashless == word {
return false;
}
replacements
.iter()
.map(|s| s.as_ref())
.any(|x| x == dashless)
}
pub(crate) fn replacements_contain_dashed<T: AsRef<str>>(word: &str, replacements: &[T]) -> bool {
if word.chars().any(|c| c == '-') {
return false;
}
replacements
.iter()
.map(|s| s.as_ref())
.filter(|s| s.as_bytes().first() == word.as_bytes().first())
.any(|s| itertools::equal(s.chars().filter(|c| *c != '-'), word.chars()))
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum Transformed<'i> {
Whitelisted((Range, &'i str)),
Fragments(Vec<(Range, &'i str)>),
Atomic((Range, &'i str)),
}
pub(crate) fn transform<'i, R: AsRef<Regex>>(
transform_regex: &[R],
word: &'i str,
range: Range,
) -> Transformed<'i> {
let mut q = std::collections::VecDeque::<(Range, &'_ str)>::with_capacity(32);
let mut words = Vec::with_capacity(16);
let mut whitelisted = 0usize;
q.push_back((range.clone(), word));
while let Some((range, word)) = q.pop_front() {
match transform_inner(transform_regex, word, range.clone()) {
Transformed::Fragments(v) => q.extend(v),
Transformed::Atomic(word) => words.push(word),
Transformed::Whitelisted(_) => whitelisted += 1,
}
}
if whitelisted == 0usize {
if words.is_empty() || (words.len() == 1 && words[0].0.len() == word.len()) {
return Transformed::Atomic((range, word));
}
}
if !words.is_empty() {
Transformed::Fragments(words)
} else {
Transformed::Whitelisted((range, word))
}
}
fn transform_inner<'i, R: AsRef<Regex>>(
transform_regex: &[R],
word: &'i str,
range: Range,
) -> Transformed<'i> {
for regex in transform_regex.iter().map(AsRef::as_ref) {
match regex.captures(word) {
Ok(Some(captures)) => {
if captures.len() == 1 {
return Transformed::Whitelisted((range, word));
}
let intermediate = captures
.iter()
.skip(1)
.flatten()
.map(|m| {
let intra_word_range = m.start()..m.end();
log::trace!(target:"quirks",
"Found capture for word >{}<, with match >{}< and capture >{}< at {:?}",
captures.get(0).unwrap().as_str(),
word,
m.as_str(),
&intra_word_range
);
let offset = word
.char_indices()
.take_while(|(byte_pos, _)| m.start() > *byte_pos)
.count();
let range = Range {
start: range.start + offset,
end: range.start + offset + m.as_str().chars().count(),
};
(range, &word[intra_word_range])
})
.collect::<Vec<_>>();
return Transformed::Fragments(intermediate);
}
Ok(None) => {
continue;
}
Err(e) => {
log::warn!(target:"quirks", "Matching regex >{}< errored: {}", regex.as_str(), e);
break;
}
}
}
Transformed::Atomic((range, word))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::WrappedRegex;
use env_logger;
#[test]
fn dashed() {
let _ = env_logger::builder()
.is_test(true)
.filter(None, log::LevelFilter::Trace)
.try_init();
const REPLACEMENTS: &'static [&'static str] = &["fffff", "qqq", "z", "zeta-ray"];
const WORD: &str = "zetaray";
assert!(replacements_contain_dashed(WORD, REPLACEMENTS));
}
#[test]
fn dashless() {
let _ = env_logger::builder()
.is_test(true)
.filter(None, log::LevelFilter::Trace)
.try_init();
const WORD: &str = "zeta-ray";
const REPLACEMENTS: &'static [&'static str] = &["fffff", "qqq", "z", "zetaray"];
assert!(replacements_contain_dashless(WORD, REPLACEMENTS));
}
#[test]
fn transformer() {
let _ = env_logger::builder()
.is_test(true)
.filter(None, log::LevelFilter::Trace)
.try_init();
let re = vec![
WrappedRegex::from(Regex::new("^[0-9]+x$").unwrap()), WrappedRegex::from(Regex::new(r#"^'([^\s]+)'$"#).unwrap()),
WrappedRegex::from(Regex::new("(Alpha)(beta)").unwrap()),
];
let words = ["2x", r#"''so-to-speak''"#, "Alphabeta", "Nothing"];
assert_eq!(
transform(re.as_slice(), words[0], 10..24),
Transformed::Whitelisted((10..24, words[0]))
);
assert_eq!(
transform(re.as_slice(), words[1], 10..25),
Transformed::Fragments(vec![(12..23, &words[1][2..13])])
);
assert_eq!(
transform(re.as_slice(), words[2], 10..19),
Transformed::Fragments(vec![(10..15, &words[2][0..5]), (15..19, &words[2][5..9]),])
);
assert_eq!(
transform(re.as_slice(), words[3], 10..17),
Transformed::Atomic((10..17, words[3]))
);
}
}