#[derive(Debug, Clone)]
pub struct CompMatcher {
pub line_pattern: String,
pub word_pattern: String,
pub flags: MatchFlags,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct MatchFlags {
pub case_insensitive: bool,
pub partial_word: bool,
pub anchor_start: bool,
pub anchor_end: bool,
pub substring: bool,
}
#[derive(Debug, Clone)]
pub struct CompLine {
pub prefix: String,
pub line: String,
pub suffix: String,
pub word: String,
pub matched: bool,
}
impl CompLine {
pub fn new() -> Self {
CompLine {
prefix: String::new(),
line: String::new(),
suffix: String::new(),
word: String::new(),
matched: false,
}
}
pub fn sublen(&self) -> usize {
self.prefix.len() + self.line.len() + self.suffix.len()
}
pub fn setlens(&mut self) {
}
}
impl Default for CompLine {
fn default() -> Self {
Self::new()
}
}
pub fn cpatterns_same(a: &[CompMatcher], b: &[CompMatcher]) -> bool {
if a.len() != b.len() {
return false;
}
a.iter()
.zip(b.iter())
.all(|(ma, mb)| ma.line_pattern == mb.line_pattern && ma.word_pattern == mb.word_pattern)
}
pub fn cmatchers_same(a: &[CompMatcher], b: &[CompMatcher]) -> bool {
cpatterns_same(a, b)
}
pub fn match_str(
line: &str,
word: &str,
matchers: &[CompMatcher],
flags: &MatchFlags,
) -> Option<Vec<CompLine>> {
if flags.case_insensitive {
if line.to_lowercase().starts_with(&word.to_lowercase()) {
return Some(vec![CompLine {
line: line.to_string(),
word: word.to_string(),
matched: true,
..Default::default()
}]);
}
} else if line.starts_with(word) {
return Some(vec![CompLine {
line: line.to_string(),
word: word.to_string(),
matched: true,
..Default::default()
}]);
}
for matcher in matchers {
if try_matcher(line, word, matcher) {
return Some(vec![CompLine {
line: line.to_string(),
word: word.to_string(),
matched: true,
..Default::default()
}]);
}
}
None
}
fn try_matcher(line: &str, word: &str, matcher: &CompMatcher) -> bool {
if matcher.flags.case_insensitive {
line.to_lowercase().contains(&word.to_lowercase())
} else if matcher.flags.substring {
line.contains(word)
} else if matcher.flags.partial_word {
let mut li = line.chars().peekable();
let mut wi = word.chars();
let mut wc = wi.next();
while let Some(lc) = li.next() {
if let Some(w) = wc {
if lc.eq_ignore_ascii_case(&w) {
wc = wi.next();
}
} else {
return true;
}
}
wc.is_none()
} else {
false
}
}
pub fn match_parts(line: &str, word: &str, flags: &MatchFlags) -> Vec<(usize, usize)> {
let mut parts = Vec::new();
let line_lower = if flags.case_insensitive {
line.to_lowercase()
} else {
line.to_string()
};
let word_lower = if flags.case_insensitive {
word.to_lowercase()
} else {
word.to_string()
};
let mut pos = 0;
for wc in word_lower.chars() {
if let Some(found) = line_lower[pos..].find(wc) {
let abs_pos = pos + found;
parts.push((abs_pos, abs_pos + wc.len_utf8()));
pos = abs_pos + wc.len_utf8();
}
}
parts
}
pub fn comp_match(line: &str, word: &str, flags: &MatchFlags) -> bool {
match_str(line, word, &[], flags).is_some()
}
pub fn start_match() -> Vec<CompLine> {
Vec::new()
}
pub fn abort_match(_lines: Vec<CompLine>) {
}
pub fn cp_cline(line: &CompLine) -> CompLine {
line.clone()
}
pub fn free_cline(_line: CompLine) {}
pub fn revert_cline(line: &mut CompLine) {
line.matched = false;
}
pub fn cline_matched(line: &CompLine) -> bool {
line.matched
}
pub fn pattern_match_equivalence(a: char, b: char, case_insensitive: bool) -> bool {
if case_insensitive {
a.eq_ignore_ascii_case(&b)
} else {
a == b
}
}
pub fn parse_matcher_spec(spec: &str) -> Vec<CompMatcher> {
let mut matchers = Vec::new();
for part in spec.split_whitespace() {
let flags = MatchFlags {
case_insensitive: part.starts_with("m:"),
partial_word: part.starts_with("r:") || part.starts_with("l:"),
anchor_start: part.starts_with("l:"),
anchor_end: part.starts_with("r:"),
substring: part.starts_with("M:"),
};
if let Some((line_pat, word_pat)) = part.split_once('=') {
let line_pat = line_pat.split(':').last().unwrap_or("");
matchers.push(CompMatcher {
line_pattern: line_pat.to_string(),
word_pattern: word_pat.to_string(),
flags,
});
}
}
matchers
}
pub fn update_bmatchers(matchers: &mut Vec<CompMatcher>, new: Vec<CompMatcher>) {
*matchers = new;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_match_str_exact() {
let flags = MatchFlags::default();
assert!(match_str("foobar", "foo", &[], &flags).is_some());
assert!(match_str("foobar", "baz", &[], &flags).is_none());
}
#[test]
fn test_match_str_case_insensitive() {
let flags = MatchFlags {
case_insensitive: true,
..Default::default()
};
assert!(match_str("FooBar", "foo", &[], &flags).is_some());
}
#[test]
fn test_match_parts() {
let flags = MatchFlags::default();
let parts = match_parts("foobar", "fbr", &flags);
assert_eq!(parts.len(), 3);
}
#[test]
fn test_pattern_match_equivalence() {
assert!(pattern_match_equivalence('a', 'A', true));
assert!(!pattern_match_equivalence('a', 'A', false));
}
#[test]
fn test_parse_matcher_spec() {
let matchers = parse_matcher_spec("m:{[:lower:]}={[:upper:]}");
assert_eq!(matchers.len(), 1);
assert!(matchers[0].flags.case_insensitive);
}
#[test]
fn test_comp_line() {
let mut cl = CompLine::new();
cl.prefix = "pre".to_string();
cl.line = "middle".to_string();
cl.suffix = "suf".to_string();
assert_eq!(cl.sublen(), 12);
}
}