|
| 1 | +use ariadne::{sources, Color, Label, Report, ReportKind}; |
1 | 2 | use ass_core::parser::{ast::Event, Script}; |
2 | 3 | use chumsky::prelude::*; |
3 | | -use similar::TextDiff; |
4 | 4 |
|
5 | 5 | /// Parse an ASS file using the ass-core parser |
6 | 6 | pub fn parse_ass(content: &str) -> Result<Script<'_>, String> { |
@@ -194,21 +194,92 @@ fn escape_debug_str(input: &str) -> String { |
194 | 194 | format!("{}", input.escape_debug()) |
195 | 195 | } |
196 | 196 |
|
| 197 | +fn diff_spans(expected: &str, actual: &str) -> ((usize, usize), (usize, usize)) { |
| 198 | + if expected == actual { |
| 199 | + let len = expected.len(); |
| 200 | + return ((len, len), (len, len)); |
| 201 | + } |
| 202 | + |
| 203 | + let mut prefix_bytes = 0; |
| 204 | + for (exp_char, act_char) in expected.chars().zip(actual.chars()) { |
| 205 | + if exp_char == act_char { |
| 206 | + prefix_bytes += exp_char.len_utf8(); |
| 207 | + } else { |
| 208 | + break; |
| 209 | + } |
| 210 | + } |
| 211 | + |
| 212 | + let expected_tail = &expected[prefix_bytes..]; |
| 213 | + let actual_tail = &actual[prefix_bytes..]; |
| 214 | + |
| 215 | + let mut suffix_bytes = 0; |
| 216 | + let mut expected_tail_iter = expected_tail.chars().rev(); |
| 217 | + let mut actual_tail_iter = actual_tail.chars().rev(); |
| 218 | + loop { |
| 219 | + match (expected_tail_iter.next(), actual_tail_iter.next()) { |
| 220 | + (Some(exp_char), Some(act_char)) if exp_char == act_char => { |
| 221 | + suffix_bytes += exp_char.len_utf8(); |
| 222 | + |
| 223 | + if prefix_bytes + suffix_bytes >= expected.len() |
| 224 | + || prefix_bytes + suffix_bytes >= actual.len() |
| 225 | + { |
| 226 | + break; |
| 227 | + } |
| 228 | + } |
| 229 | + _ => break, |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + let mut expected_end = expected.len().saturating_sub(suffix_bytes); |
| 234 | + let mut actual_end = actual.len().saturating_sub(suffix_bytes); |
| 235 | + |
| 236 | + if expected_end < prefix_bytes { |
| 237 | + expected_end = prefix_bytes; |
| 238 | + } |
| 239 | + if actual_end < prefix_bytes { |
| 240 | + actual_end = prefix_bytes; |
| 241 | + } |
| 242 | + |
| 243 | + ((prefix_bytes, expected_end), (prefix_bytes, actual_end)) |
| 244 | +} |
| 245 | + |
197 | 246 | fn format_diff_output(expected: &str, actual: &str) -> String { |
198 | | - let expected_display = format!("{}\n", escape_debug_str(expected)); |
199 | | - let actual_display = format!("{}\n", escape_debug_str(actual)); |
| 247 | + let expected_display = escape_debug_str(expected); |
| 248 | + let actual_display = escape_debug_str(actual); |
| 249 | + |
| 250 | + let ((expected_start, expected_end), (actual_start, actual_end)) = |
| 251 | + diff_spans(&expected_display, &actual_display); |
| 252 | + |
| 253 | + let report = Report::build( |
| 254 | + ReportKind::Error, |
| 255 | + ("actual.ass", actual_start..actual_start), |
| 256 | + ) |
| 257 | + .with_message("ASS text mismatch") |
| 258 | + .with_label( |
| 259 | + Label::new(("expected.ass", expected_start..expected_end)) |
| 260 | + .with_message("expected segment") |
| 261 | + .with_color(Color::Yellow), |
| 262 | + ) |
| 263 | + .with_label( |
| 264 | + Label::new(("actual.ass", actual_start..actual_end)) |
| 265 | + .with_message("actual segment") |
| 266 | + .with_color(Color::Cyan), |
| 267 | + ) |
| 268 | + .finish(); |
200 | 269 |
|
201 | | - let diff = TextDiff::from_lines(&expected_display, &actual_display); |
202 | 270 | let mut buffer = Vec::new(); |
203 | | - diff.unified_diff() |
204 | | - .context_radius(0) |
205 | | - .header("expected", "actual") |
206 | | - .to_writer(&mut buffer) |
207 | | - .expect("writing diff to buffer"); |
208 | | - |
209 | | - let diff_string = |
210 | | - String::from_utf8(buffer).unwrap_or_else(|_| "<diff output not valid UTF-8>".to_string()); |
211 | | - diff_string |
| 271 | + if let Err(err) = report.write( |
| 272 | + sources([ |
| 273 | + ("expected.ass", expected_display.as_str()), |
| 274 | + ("actual.ass", actual_display.as_str()), |
| 275 | + ]), |
| 276 | + &mut buffer, |
| 277 | + ) { |
| 278 | + return format!(" <failed to render ariadne report: {}>", err); |
| 279 | + } |
| 280 | + |
| 281 | + String::from_utf8(buffer) |
| 282 | + .unwrap_or_else(|_| "<ariadne output not valid UTF-8>".to_string()) |
212 | 283 | .lines() |
213 | 284 | .map(|line| format!(" {}", line)) |
214 | 285 | .collect::<Vec<_>>() |
@@ -369,32 +440,21 @@ pub fn compare_ass_files(expected: &Script<'_>, actual: &Script<'_>) -> Result<( |
369 | 440 | FuzzyMatchResult::NumericOnly => { |
370 | 441 | let diff_view = format_diff_output(exp_event.text, act_event.text); |
371 | 442 | warnings.push(format!( |
372 | | - "Event {}: text numeric-only mismatch downgraded to warning:\n expected: \"{}\"\n actual: \"{}\"\n{}", |
373 | | - i, |
374 | | - escape_debug_str(exp_event.text), |
375 | | - escape_debug_str(act_event.text), |
376 | | - diff_view |
| 443 | + "Event {}: text numeric-only mismatch downgraded to warning:\n{}", |
| 444 | + i, diff_view |
377 | 445 | )); |
378 | 446 | } |
379 | 447 | FuzzyMatchResult::Different => { |
380 | 448 | if pos_difference_only(exp_event.text, act_event.text) { |
381 | 449 | let diff_view = format_diff_output(exp_event.text, act_event.text); |
382 | 450 | warnings.push(format!( |
383 | | - "Event {}: text mismatch limited to pos() rounding, downgraded to warning:\n expected: \"{}\"\n actual: \"{}\"\n{}", |
| 451 | + "Event {}: text mismatch limited to pos() rounding, downgraded to warning:\n{}", |
384 | 452 | i, |
385 | | - escape_debug_str(exp_event.text), |
386 | | - escape_debug_str(act_event.text), |
387 | 453 | diff_view |
388 | 454 | )); |
389 | 455 | } else { |
390 | 456 | let diff_view = format_diff_output(exp_event.text, act_event.text); |
391 | | - errors.push(format!( |
392 | | - "Event {}: text mismatch:\n expected: \"{}\"\n actual: \"{}\"\n{}", |
393 | | - i, |
394 | | - escape_debug_str(exp_event.text), |
395 | | - escape_debug_str(act_event.text), |
396 | | - diff_view |
397 | | - )); |
| 457 | + errors.push(format!("Event {}: text mismatch:\n{}", i, diff_view)); |
398 | 458 | } |
399 | 459 | } |
400 | 460 | } |
|
0 commit comments