| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub(crate) fn seek_sequence( |
| lines: &[String], |
| pattern: &[String], |
| start: usize, |
| eof: bool, |
| update_file_mode: crate::ApplyPatchFileUpdateMode, |
| ) -> Option<usize> { |
| if pattern.is_empty() { |
| return Some(start); |
| } |
|
|
| |
| |
| |
| |
| if pattern.len() > lines.len() { |
| return None; |
| } |
| let search_start = if eof && lines.len() >= pattern.len() { |
| let eof_start = lines.len() - pattern.len(); |
| match update_file_mode { |
| crate::ApplyPatchFileUpdateMode::NormalizeToLf => eof_start, |
| crate::ApplyPatchFileUpdateMode::PreserveLineEndings => eof_start.max(start), |
| } |
| } else { |
| start |
| }; |
| |
| for i in search_start..=lines.len().saturating_sub(pattern.len()) { |
| if lines[i..i + pattern.len()] == *pattern { |
| return Some(i); |
| } |
| } |
| |
| for i in search_start..=lines.len().saturating_sub(pattern.len()) { |
| let mut ok = true; |
| for (p_idx, pat) in pattern.iter().enumerate() { |
| if lines[i + p_idx].trim_end() != pat.trim_end() { |
| ok = false; |
| break; |
| } |
| } |
| if ok { |
| return Some(i); |
| } |
| } |
| |
| for i in search_start..=lines.len().saturating_sub(pattern.len()) { |
| let mut ok = true; |
| for (p_idx, pat) in pattern.iter().enumerate() { |
| if lines[i + p_idx].trim() != pat.trim() { |
| ok = false; |
| break; |
| } |
| } |
| if ok { |
| return Some(i); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| fn normalise(s: &str) -> String { |
| s.trim() |
| .chars() |
| .map(|c| match c { |
| |
| '\u{2010}' | '\u{2011}' | '\u{2012}' | '\u{2013}' | '\u{2014}' | '\u{2015}' |
| | '\u{2212}' => '-', |
| |
| '\u{2018}' | '\u{2019}' | '\u{201A}' | '\u{201B}' => '\'', |
| |
| '\u{201C}' | '\u{201D}' | '\u{201E}' | '\u{201F}' => '"', |
| |
| '\u{00A0}' | '\u{2002}' | '\u{2003}' | '\u{2004}' | '\u{2005}' | '\u{2006}' |
| | '\u{2007}' | '\u{2008}' | '\u{2009}' | '\u{200A}' | '\u{202F}' | '\u{205F}' |
| | '\u{3000}' => ' ', |
| other => other, |
| }) |
| .collect::<String>() |
| } |
|
|
| for i in search_start..=lines.len().saturating_sub(pattern.len()) { |
| let mut ok = true; |
| for (p_idx, pat) in pattern.iter().enumerate() { |
| if normalise(&lines[i + p_idx]) != normalise(pat) { |
| ok = false; |
| break; |
| } |
| } |
| if ok { |
| return Some(i); |
| } |
| } |
|
|
| None |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::seek_sequence; |
| use crate::ApplyPatchFileUpdateMode; |
| use std::string::ToString; |
|
|
| fn to_vec(strings: &[&str]) -> Vec<String> { |
| strings.iter().map(ToString::to_string).collect() |
| } |
|
|
| #[test] |
| fn test_exact_match_finds_sequence() { |
| let lines = to_vec(&["foo", "bar", "baz"]); |
| let pattern = to_vec(&["bar", "baz"]); |
| assert_eq!( |
| seek_sequence( |
| &lines, |
| &pattern, |
| 0, |
| false, |
| ApplyPatchFileUpdateMode::NormalizeToLf, |
| ), |
| Some(1) |
| ); |
| } |
|
|
| #[test] |
| fn test_rstrip_match_ignores_trailing_whitespace() { |
| let lines = to_vec(&["foo ", "bar\t\t"]); |
| |
| let pattern = to_vec(&["foo", "bar"]); |
| assert_eq!( |
| seek_sequence( |
| &lines, |
| &pattern, |
| 0, |
| false, |
| ApplyPatchFileUpdateMode::NormalizeToLf, |
| ), |
| Some(0) |
| ); |
| } |
|
|
| #[test] |
| fn test_trim_match_ignores_leading_and_trailing_whitespace() { |
| let lines = to_vec(&[" foo ", " bar\t"]); |
| |
| let pattern = to_vec(&["foo", "bar"]); |
| assert_eq!( |
| seek_sequence( |
| &lines, |
| &pattern, |
| 0, |
| false, |
| ApplyPatchFileUpdateMode::NormalizeToLf, |
| ), |
| Some(0) |
| ); |
| } |
|
|
| #[test] |
| fn test_pattern_longer_than_input_returns_none() { |
| let lines = to_vec(&["just one line"]); |
| let pattern = to_vec(&["too", "many", "lines"]); |
| |
| assert_eq!( |
| seek_sequence( |
| &lines, |
| &pattern, |
| 0, |
| false, |
| ApplyPatchFileUpdateMode::NormalizeToLf, |
| ), |
| None |
| ); |
| } |
| } |
|
|