From 17d40c6057c88f4c432b0d7bac88e1b84cb7e67f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:03:36 +0200 Subject: Adding upstream version 1.65.0+dfsg1. Signed-off-by: Daniel Baumann --- vendor/pretty_assertions/src/printer.rs | 474 ++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 vendor/pretty_assertions/src/printer.rs (limited to 'vendor/pretty_assertions/src/printer.rs') diff --git a/vendor/pretty_assertions/src/printer.rs b/vendor/pretty_assertions/src/printer.rs new file mode 100644 index 000000000..172b1ed6c --- /dev/null +++ b/vendor/pretty_assertions/src/printer.rs @@ -0,0 +1,474 @@ +use ansi_term::{ + Colour::{Fixed, Green, Red}, + Style, +}; +use std::fmt; + +macro_rules! paint { + ($f:expr, $colour:expr, $fmt:expr, $($args:tt)*) => ( + write!($f, "{}", $colour.paint(format!($fmt, $($args)*))) + ) +} + +const SIGN_RIGHT: char = '>'; // + > → +const SIGN_LEFT: char = '<'; // - < ← + +/// Present the diff output for two mutliline strings in a pretty, colorised manner. +pub(crate) fn write_header(f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "{} {} / {} :", + Style::new().bold().paint("Diff"), + Red.paint(format!("{} left", SIGN_LEFT)), + Green.paint(format!("right {}", SIGN_RIGHT)) + ) +} + +/// Delay formatting this deleted chunk until later. +/// +/// It can be formatted as a whole chunk by calling `flush`, or the inner value +/// obtained with `take` for further processing. +#[derive(Default)] +struct LatentDeletion<'a> { + // The most recent deleted line we've seen + value: Option<&'a str>, + // The number of deleted lines we've seen, including the current value + count: usize, +} + +impl<'a> LatentDeletion<'a> { + /// Set the chunk value. + fn set(&mut self, value: &'a str) { + self.value = Some(value); + self.count += 1; + } + + /// Take the underlying chunk value, if it's suitable for inline diffing. + /// + /// If there is no value of we've seen more than one line, return `None`. + fn take(&mut self) -> Option<&'a str> { + if self.count == 1 { + self.value.take() + } else { + None + } + } + + /// If a value is set, print it as a whole chunk, using the given formatter. + /// + /// If a value is not set, reset the count to zero (as we've called `flush` twice, + /// without seeing another deletion. Therefore the line in the middle was something else). + fn flush(&mut self, f: &mut TWrite) -> fmt::Result { + if let Some(value) = self.value { + paint!(f, Red, "{}{}", SIGN_LEFT, value)?; + writeln!(f)?; + self.value = None; + } else { + self.count = 0; + } + + Ok(()) + } +} + +// Adapted from: +// https://github.com/johannhof/difference.rs/blob/c5749ad7d82aa3d480c15cb61af9f6baa08f116f/examples/github-style.rs +// Credits johannhof (MIT License) + +/// Present the diff output for two mutliline strings in a pretty, colorised manner. +pub(crate) fn write_lines( + f: &mut TWrite, + left: &str, + right: &str, +) -> fmt::Result { + let diff = ::diff::lines(left, right); + + let mut changes = diff.into_iter().peekable(); + let mut previous_deletion = LatentDeletion::default(); + + while let Some(change) = changes.next() { + match (change, changes.peek()) { + // If the text is unchanged, just print it plain + (::diff::Result::Both(value, _), _) => { + previous_deletion.flush(f)?; + writeln!(f, " {}", value)?; + } + // Defer any deletions to next loop + (::diff::Result::Left(deleted), _) => { + previous_deletion.flush(f)?; + previous_deletion.set(deleted); + } + // Underlying diff library should never return this sequence + (::diff::Result::Right(_), Some(::diff::Result::Left(_))) => { + panic!("insertion followed by deletion"); + } + // If we're being followed by more insertions, don't inline diff + (::diff::Result::Right(inserted), Some(::diff::Result::Right(_))) => { + previous_deletion.flush(f)?; + paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?; + writeln!(f)?; + } + // Otherwise, check if we need to inline diff with the previous line (if it was a deletion) + (::diff::Result::Right(inserted), _) => { + if let Some(deleted) = previous_deletion.take() { + write_inline_diff(f, deleted, inserted)?; + } else { + previous_deletion.flush(f)?; + paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?; + writeln!(f)?; + } + } + }; + } + + previous_deletion.flush(f)?; + Ok(()) +} + +/// Group character styling for an inline diff, to prevent wrapping each single +/// character in terminal styling codes. +/// +/// Styles are applied automatically each time a new style is given in `write_with_style`. +struct InlineWriter<'a, Writer> { + f: &'a mut Writer, + style: Style, +} + +impl<'a, Writer> InlineWriter<'a, Writer> +where + Writer: fmt::Write, +{ + fn new(f: &'a mut Writer) -> Self { + InlineWriter { + f, + style: Style::new(), + } + } + + /// Push a new character into the buffer, specifying the style it should be written in. + fn write_with_style(&mut self, c: &char, style: Style) -> fmt::Result { + // If the style is the same as previously, just write character + if style == self.style { + write!(self.f, "{}", c)?; + } else { + // Close out previous style + write!(self.f, "{}", self.style.suffix())?; + + // Store new style and start writing it + write!(self.f, "{}{}", style.prefix(), c)?; + self.style = style; + } + Ok(()) + } + + /// Finish any existing style and reset to default state. + fn finish(&mut self) -> fmt::Result { + // Close out previous style + writeln!(self.f, "{}", self.style.suffix())?; + self.style = Default::default(); + Ok(()) + } +} + +/// Format a single line to show an inline diff of the two strings given. +/// +/// The given strings should not have a trailing newline. +/// +/// The output of this function will be two lines, each with a trailing newline. +fn write_inline_diff(f: &mut TWrite, left: &str, right: &str) -> fmt::Result { + let diff = ::diff::chars(left, right); + let mut writer = InlineWriter::new(f); + + // Print the left string on one line, with differences highlighted + let light = Red.into(); + let heavy = Red.on(Fixed(52)).bold(); + writer.write_with_style(&SIGN_LEFT, light)?; + for change in diff.iter() { + match change { + ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?, + ::diff::Result::Left(value) => writer.write_with_style(value, heavy)?, + _ => (), + } + } + writer.finish()?; + + // Print the right string on one line, with differences highlighted + let light = Green.into(); + let heavy = Green.on(Fixed(22)).bold(); + writer.write_with_style(&SIGN_RIGHT, light)?; + for change in diff.iter() { + match change { + ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?, + ::diff::Result::Right(value) => writer.write_with_style(value, heavy)?, + _ => (), + } + } + writer.finish() +} + +#[cfg(test)] +mod test { + use super::*; + + // ANSI terminal codes used in our outputs. + // + // Interpolate these into test strings to make expected values easier to read. + const RED_LIGHT: &str = "\u{1b}[31m"; + const GREEN_LIGHT: &str = "\u{1b}[32m"; + const RED_HEAVY: &str = "\u{1b}[1;48;5;52;31m"; + const GREEN_HEAVY: &str = "\u{1b}[1;48;5;22;32m"; + const RESET: &str = "\u{1b}[0m"; + + /// Given that both of our diff printing functions have the same + /// type signature, we can reuse the same test code for them. + /// + /// This could probably be nicer with traits! + fn check_printer(printer: TPrint, left: &str, right: &str, expected: &str) + where + TPrint: Fn(&mut String, &str, &str) -> fmt::Result, + { + let mut actual = String::new(); + printer(&mut actual, left, right).expect("printer function failed"); + + println!( + "## left ##\n\ + {}\n\ + ## right ##\n\ + {}\n\ + ## actual diff ##\n\ + {}\n\ + ## expected diff ##\n\ + {}", + left, right, actual, expected + ); + assert_eq!(actual, expected); + } + + #[test] + fn write_inline_diff_empty() { + let left = ""; + let right = ""; + let expected = format!( + "{red_light}<{reset}\n\ + {green_light}>{reset}\n", + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer(write_inline_diff, left, right, &expected); + } + + #[test] + fn write_inline_diff_added() { + let left = ""; + let right = "polymerase"; + let expected = format!( + "{red_light}<{reset}\n\ + {green_light}>{reset}{green_heavy}polymerase{reset}\n", + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + green_heavy = GREEN_HEAVY, + reset = RESET, + ); + + check_printer(write_inline_diff, left, right, &expected); + } + + #[test] + fn write_inline_diff_removed() { + let left = "polyacrylamide"; + let right = ""; + let expected = format!( + "{red_light}<{reset}{red_heavy}polyacrylamide{reset}\n\ + {green_light}>{reset}\n", + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + red_heavy = RED_HEAVY, + reset = RESET, + ); + + check_printer(write_inline_diff, left, right, &expected); + } + + #[test] + fn write_inline_diff_changed() { + let left = "polymerase"; + let right = "polyacrylamide"; + let expected = format!( + "{red_light}poly{reset}{green_heavy}ac{reset}{green_light}r{reset}{green_heavy}yl{reset}{green_light}a{reset}{green_heavy}mid{reset}{green_light}e{reset}\n", + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + red_heavy = RED_HEAVY, + green_heavy = GREEN_HEAVY, + reset = RESET, + ); + + check_printer(write_inline_diff, left, right, &expected); + } + + /// If one of our strings is empty, it should not be shown at all in the output. + #[test] + fn write_lines_empty_string() { + let left = ""; + let right = "content"; + let expected = format!( + "{green_light}>content{reset}\n", + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer(write_lines, left, right, &expected); + } + + /// Realistic multiline struct diffing case. + #[test] + fn write_lines_struct() { + let left = r#"Some( + Foo { + lorem: "Hello World!", + ipsum: 42, + dolor: Ok( + "hey", + ), + }, +)"#; + let right = r#"Some( + Foo { + lorem: "Hello Wrold!", + ipsum: 42, + dolor: Ok( + "hey ho!", + ), + }, +)"#; + let expected = format!( + r#" Some( + Foo {{ +{red_light}< lorem: "Hello W{reset}{red_heavy}o{reset}{red_light}rld!",{reset} +{green_light}> lorem: "Hello Wr{reset}{green_heavy}o{reset}{green_light}ld!",{reset} + ipsum: 42, + dolor: Ok( +{red_light}< "hey",{reset} +{green_light}> "hey{reset}{green_heavy} ho!{reset}{green_light}",{reset} + ), + }}, + ) +"#, + red_light = RED_LIGHT, + red_heavy = RED_HEAVY, + green_light = GREEN_LIGHT, + green_heavy = GREEN_HEAVY, + reset = RESET, + ); + + check_printer(write_lines, left, right, &expected); + } + + /// Relistic multiple line chunks + /// + /// We can't support realistic line diffing in large blocks + /// (also, it's unclear how usefult this is) + /// + /// So if we have more than one line in a single removal chunk, disable inline diffing. + #[test] + fn write_lines_multiline_block() { + let left = r#"Proboscis +Cabbage"#; + let right = r#"Probed +Caravaggio"#; + let expected = format!( + r#"{red_light}Probed{reset} +{green_light}>Caravaggio{reset} +"#, + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer(write_lines, left, right, &expected); + } + + /// Single deletion line, multiple insertions - no inline diffing. + #[test] + fn write_lines_multiline_insert() { + let left = r#"Cabbage"#; + let right = r#"Probed +Caravaggio"#; + let expected = format!( + r#"{red_light}Probed{reset} +{green_light}>Caravaggio{reset} +"#, + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer(write_lines, left, right, &expected); + } + + /// Multiple deletion, single insertion - no inline diffing. + #[test] + fn write_lines_multiline_delete() { + let left = r#"Proboscis +Cabbage"#; + let right = r#"Probed"#; + let expected = format!( + r#"{red_light}Probed{reset} +"#, + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer(write_lines, left, right, &expected); + } + + /// Regression test for multiline highlighting issue + #[test] + fn write_lines_issue12() { + let left = r#"[ + 0, + 0, + 0, + 128, + 10, + 191, + 5, + 64, +]"#; + let right = r#"[ + 84, + 248, + 45, + 64, +]"#; + let expected = format!( + r#" [ +{red_light}< 0,{reset} +{red_light}< 0,{reset} +{red_light}< 0,{reset} +{red_light}< 128,{reset} +{red_light}< 10,{reset} +{red_light}< 191,{reset} +{red_light}< 5,{reset} +{green_light}> 84,{reset} +{green_light}> 248,{reset} +{green_light}> 45,{reset} + 64, + ] +"#, + red_light = RED_LIGHT, + green_light = GREEN_LIGHT, + reset = RESET, + ); + + check_printer(write_lines, left, right, &expected); + } +} -- cgit v1.2.3