Skip to content

Commit

Permalink
Merge pull request #279 from epage/regex
Browse files Browse the repository at this point in the history
fix(snap): Change how we normalize matches
  • Loading branch information
epage authored Apr 19, 2024
2 parents 932de19 + a1995e5 commit 52db121
Showing 1 changed file with 75 additions and 107 deletions.
182 changes: 75 additions & 107 deletions crates/snapbox/src/substitutions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,42 @@ impl Substitutions {
normalize(input, pattern, self)
}

fn substitute<'v>(&self, value: &'v str) -> Cow<'v, str> {
let mut value = Cow::Borrowed(value);
for (var, replace) in self.vars.iter() {
for replace in replace {
debug_assert!(!replace.is_empty());
value = Cow::Owned(value.replace(replace.as_ref(), var));
}
}
value
fn substitute(&self, input: &str) -> String {
let mut input = input.to_owned();
replace_many(
&mut input,
self.vars.iter().flat_map(|(var, replaces)| {
replaces.iter().map(|replace| (*var, replace.as_ref()))
}),
);
input
}

fn clear<'v>(&self, pattern: &'v str) -> Cow<'v, str> {
if pattern.contains('[') {
let mut pattern = Cow::Borrowed(pattern);
for var in self.unused.iter() {
pattern = Cow::Owned(pattern.replace(var, ""));
}
pattern
if !self.unused.is_empty() && pattern.contains('[') {
let mut pattern = pattern.to_owned();
replace_many(&mut pattern, self.unused.iter().map(|var| (*var, "")));
Cow::Owned(pattern)
} else {
Cow::Borrowed(pattern)
}
}
}

fn replace_many<'a>(
buffer: &mut String,
replacements: impl IntoIterator<Item = (&'a str, &'a str)>,
) {
for (var, replace) in replacements {
let mut index = 0;
while let Some(offset) = buffer[index..].find(var) {
let old_range = (index + offset)..(index + offset + var.len());
buffer.replace_range(old_range, replace);
index += offset + replace.len();
}
}
}

fn validate_key(key: &'static str) -> Result<&'static str, crate::Error> {
if !key.starts_with('[') || !key.ends_with(']') {
return Err(format!("Key `{}` is not enclosed in []", key).into());
Expand All @@ -128,122 +140,69 @@ fn normalize(input: &str, pattern: &str, substitutions: &Substitutions) -> Strin
return input.to_owned();
}

let mut normalized: Vec<Cow<str>> = Vec::new();
let input_lines: Vec<_> = crate::utils::LinesWithTerminator::new(input).collect();
let pattern_lines: Vec<_> = crate::utils::LinesWithTerminator::new(pattern).collect();
let input = substitutions.substitute(input);

let mut normalized: Vec<&str> = Vec::new();
let mut input_index = 0;
let mut pattern_index = 0;
'outer: loop {
let pattern_line = if let Some(pattern_line) = pattern_lines.get(pattern_index) {
*pattern_line
} else {
normalized.extend(
input_lines[input_index..]
.iter()
.copied()
.map(|s| substitutions.substitute(s)),
);
break 'outer;
};
let next_pattern_index = pattern_index + 1;

let input_line = if let Some(input_line) = input_lines.get(input_index) {
*input_line
} else {
break 'outer;
};
let next_input_index = input_index + 1;

if line_matches(input_line, pattern_line, substitutions) {
pattern_index = next_pattern_index;
input_index = next_input_index;
normalized.push(Cow::Borrowed(pattern_line));
continue 'outer;
} else if is_line_elide(pattern_line) {
let next_pattern_line: &str =
if let Some(pattern_line) = pattern_lines.get(next_pattern_index) {
pattern_line
} else {
normalized.push(Cow::Borrowed(pattern_line));
break 'outer;
};
if let Some(future_input_index) = input_lines[input_index..]
.iter()
.enumerate()
.find(|(_, l)| **l == next_pattern_line)
.map(|(i, _)| input_index + i)
{
normalized.push(Cow::Borrowed(pattern_line));
pattern_index = next_pattern_index;
input_index = future_input_index;
continue 'outer;
let input_lines: Vec<_> = crate::utils::LinesWithTerminator::new(&input).collect();
let mut pattern_lines = crate::utils::LinesWithTerminator::new(pattern).peekable();
'outer: while let Some(pattern_line) = pattern_lines.next() {
if is_line_elide(pattern_line) {
if let Some(next_pattern_line) = pattern_lines.peek() {
for (index_offset, next_input_line) in input_lines[input_index..].iter().copied().enumerate() {
if line_matches(next_input_line, next_pattern_line, substitutions) {
normalized.push(pattern_line);
input_index += index_offset;
continue 'outer;
}
}
// Give up doing further normalization
break;
} else {
normalized.extend(
input_lines[input_index..]
.iter()
.copied()
.map(|s| substitutions.substitute(s)),
);
break 'outer;
// Give up doing further normalization
normalized.push(pattern_line);
// captured rest so don't copy remaining lines over
input_index = input_lines.len();
break;
}
} else {
// Find where we can pick back up for normalizing
for future_input_index in next_input_index..input_lines.len() {
let future_input_line = input_lines[future_input_index];
if let Some(future_pattern_index) = pattern_lines[next_pattern_index..]
.iter()
.enumerate()
.find(|(_, l)| **l == future_input_line || is_line_elide(l))
.map(|(i, _)| next_pattern_index + i)
{
normalized.extend(
input_lines[input_index..future_input_index]
.iter()
.copied()
.map(|s| substitutions.substitute(s)),
);
pattern_index = future_pattern_index;
input_index = future_input_index;
continue 'outer;
}
let Some(input_line) = input_lines.get(input_index) else {
// Give up doing further normalization
break;
};

if line_matches(input_line, pattern_line, substitutions) {
input_index += 1;
normalized.push(pattern_line);
} else {
// Give up doing further normalization
break;
}

normalized.extend(
input_lines[input_index..]
.iter()
.copied()
.map(|s| substitutions.substitute(s)),
);
break 'outer;
}
}

normalized.extend(input_lines[input_index..].iter().copied());
normalized.join("")
}

fn is_line_elide(line: &str) -> bool {
line == "...\n" || line == "..."
}

fn line_matches(line: &str, pattern: &str, substitutions: &Substitutions) -> bool {
if line == pattern {
fn line_matches(mut input: &str, pattern: &str, substitutions: &Substitutions) -> bool {
if input == pattern {
return true;
}

let subbed = substitutions.substitute(line);
let mut line = subbed.as_ref();

let pattern = substitutions.clear(pattern);

let mut sections = pattern.split("[..]").peekable();
while let Some(section) = sections.next() {
if let Some(remainder) = line.strip_prefix(section) {
if let Some(remainder) = input.strip_prefix(section) {
if let Some(next_section) = sections.peek() {
if next_section.is_empty() {
line = "";
input = "";
} else if let Some(restart_index) = remainder.find(next_section) {
line = &remainder[restart_index..];
input = &remainder[restart_index..];
}
} else {
return remainder.is_empty();
Expand Down Expand Up @@ -314,6 +273,15 @@ mod test {
assert_eq!(expected, actual);
}

#[test]
fn elide_delimited_with_sub() {
let input = "Hello World\nHow are you?\nGoodbye World";
let pattern = "Hello [..]\n...\nGoodbye [..]";
let expected = "Hello [..]\n...\nGoodbye [..]";
let actual = normalize(input, pattern, &Substitutions::new());
assert_eq!(expected, actual);
}

#[test]
fn leading_elide() {
let input = "Hello\nWorld\nGoodbye";
Expand Down Expand Up @@ -354,7 +322,7 @@ mod test {
fn post_diverge_elide() {
let input = "Hello\nWorld\nGoodbye\nSir";
let pattern = "Hello\nMoon\nGoodbye\n...";
let expected = "Hello\nWorld\nGoodbye\n...";
let expected = "Hello\nWorld\nGoodbye\nSir";
let actual = normalize(input, pattern, &Substitutions::new());
assert_eq!(expected, actual);
}
Expand Down

0 comments on commit 52db121

Please sign in to comment.