summaryrefslogtreecommitdiffstats
path: root/vendor/prettydiff/src/text.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 18:31:44 +0000
commitc23a457e72abe608715ac76f076f47dc42af07a5 (patch)
tree2772049aaf84b5c9d0ed12ec8d86812f7a7904b6 /vendor/prettydiff/src/text.rs
parentReleasing progress-linux version 1.73.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-c23a457e72abe608715ac76f076f47dc42af07a5.tar.xz
rustc-c23a457e72abe608715ac76f076f47dc42af07a5.zip
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/prettydiff/src/text.rs')
-rw-r--r--vendor/prettydiff/src/text.rs877
1 files changed, 877 insertions, 0 deletions
diff --git a/vendor/prettydiff/src/text.rs b/vendor/prettydiff/src/text.rs
new file mode 100644
index 000000000..0d5fc03c3
--- /dev/null
+++ b/vendor/prettydiff/src/text.rs
@@ -0,0 +1,877 @@
+//! Utils for diff text
+pub use ansi_term::Style;
+
+use crate::basic;
+cfg_prettytable! {
+ use crate::format_table;
+ use prettytable::{Cell, Row};
+}
+use ansi_term::Colour;
+use pad::{Alignment, PadStr};
+use std::{
+ cmp::{max, min},
+ fmt,
+};
+
+pub struct StringSplitIter<'a, F>
+where
+ F: Fn(char) -> bool,
+{
+ last: usize,
+ text: &'a str,
+ matched: Option<&'a str>,
+ iter: std::str::MatchIndices<'a, F>,
+}
+
+impl<'a, F> Iterator for StringSplitIter<'a, F>
+where
+ F: Fn(char) -> bool,
+{
+ type Item = &'a str;
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(m) = self.matched {
+ self.matched = None;
+ Some(m)
+ } else if let Some((idx, matched)) = self.iter.next() {
+ let res = if self.last != idx {
+ self.matched = Some(matched);
+ &self.text[self.last..idx]
+ } else {
+ matched
+ };
+ self.last = idx + matched.len();
+ Some(res)
+ } else if self.last < self.text.len() {
+ let res = &self.text[self.last..];
+ self.last = self.text.len();
+ Some(res)
+ } else {
+ None
+ }
+ }
+}
+
+pub fn collect_strings<T: ToString>(it: impl Iterator<Item = T>) -> Vec<String> {
+ it.map(|s| s.to_string()).collect::<Vec<String>>()
+}
+
+/// Split string by clousure (Fn(char)->bool) keeping delemiters
+pub fn split_by_char_fn<F>(text: &'_ str, pat: F) -> StringSplitIter<'_, F>
+where
+ F: Fn(char) -> bool,
+{
+ StringSplitIter {
+ last: 0,
+ text,
+ matched: None,
+ iter: text.match_indices(pat),
+ }
+}
+
+/// Split string by non-alphanumeric characters keeping delemiters
+pub fn split_words(text: &str) -> impl Iterator<Item = &str> {
+ split_by_char_fn(text, |c: char| !c.is_alphanumeric())
+}
+
+/// Container for inline text diff result. Can be pretty-printed by Display trait.
+#[derive(Debug, PartialEq)]
+pub struct InlineChangeset<'a> {
+ old: Vec<&'a str>,
+ new: Vec<&'a str>,
+ separator: &'a str,
+ highlight_whitespace: bool,
+ insert_style: Style,
+ insert_whitespace_style: Style,
+ remove_style: Style,
+ remove_whitespace_style: Style,
+}
+
+impl<'a> InlineChangeset<'a> {
+ pub fn new(old: Vec<&'a str>, new: Vec<&'a str>) -> InlineChangeset<'a> {
+ InlineChangeset {
+ old,
+ new,
+ separator: "",
+ highlight_whitespace: true,
+ insert_style: Colour::Green.normal(),
+ insert_whitespace_style: Colour::White.on(Colour::Green),
+ remove_style: Colour::Red.strikethrough(),
+ remove_whitespace_style: Colour::White.on(Colour::Red),
+ }
+ }
+ /// Highlight whitespaces in case of insert/remove?
+ pub fn set_highlight_whitespace(mut self, val: bool) -> Self {
+ self.highlight_whitespace = val;
+ self
+ }
+
+ /// Style of inserted text
+ pub fn set_insert_style(mut self, val: Style) -> Self {
+ self.insert_style = val;
+ self
+ }
+
+ /// Style of inserted whitespace
+ pub fn set_insert_whitespace_style(mut self, val: Style) -> Self {
+ self.insert_whitespace_style = val;
+ self
+ }
+
+ /// Style of removed text
+ pub fn set_remove_style(mut self, val: Style) -> Self {
+ self.remove_style = val;
+ self
+ }
+
+ /// Style of removed whitespace
+ pub fn set_remove_whitespace_style(mut self, val: Style) -> Self {
+ self.remove_whitespace_style = val;
+ self
+ }
+
+ /// Set output separator
+ pub fn set_separator(mut self, val: &'a str) -> Self {
+ self.separator = val;
+ self
+ }
+
+ /// Returns Vec of changes
+ pub fn diff(&self) -> Vec<basic::DiffOp<'a, &str>> {
+ basic::diff(&self.old, &self.new)
+ }
+
+ fn apply_style(&self, style: Style, whitespace_style: Style, a: &[&str]) -> String {
+ let s = a.join(self.separator);
+ if self.highlight_whitespace {
+ collect_strings(split_by_char_fn(&s, |c| c.is_whitespace()).map(|s| {
+ let style = if s
+ .chars()
+ .next()
+ .map_or_else(|| false, |c| c.is_whitespace())
+ {
+ whitespace_style
+ } else {
+ style
+ };
+ style.paint(s)
+ }))
+ .join("")
+ } else {
+ style.paint(s).to_string()
+ }
+ }
+
+ fn remove_color(&self, a: &[&str]) -> String {
+ self.apply_style(self.remove_style, self.remove_whitespace_style, a)
+ }
+
+ fn insert_color(&self, a: &[&str]) -> String {
+ self.apply_style(self.insert_style, self.insert_whitespace_style, a)
+ }
+ /// Returns formatted string with colors
+ pub fn format(&self) -> String {
+ let diff = self.diff();
+ let mut out: Vec<String> = Vec::with_capacity(diff.len());
+ for op in diff {
+ match op {
+ basic::DiffOp::Equal(a) => out.push(a.join(self.separator)),
+ basic::DiffOp::Insert(a) => out.push(self.insert_color(a)),
+ basic::DiffOp::Remove(a) => out.push(self.remove_color(a)),
+ basic::DiffOp::Replace(a, b) => {
+ out.push(self.remove_color(a));
+ out.push(self.insert_color(b));
+ }
+ }
+ }
+ out.join(self.separator)
+ }
+}
+
+impl<'a> fmt::Display for InlineChangeset<'a> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ write!(formatter, "{}", self.format())
+ }
+}
+
+pub fn diff_chars<'a>(old: &'a str, new: &'a str) -> InlineChangeset<'a> {
+ let old: Vec<&str> = old.split("").filter(|&i| i != "").collect();
+ let new: Vec<&str> = new.split("").filter(|&i| i != "").collect();
+
+ InlineChangeset::new(old, new)
+}
+
+/// Diff two strings by words (contiguous)
+pub fn diff_words<'a>(old: &'a str, new: &'a str) -> InlineChangeset<'a> {
+ InlineChangeset::new(split_words(old).collect(), split_words(new).collect())
+}
+
+#[cfg(feature = "prettytable-rs")]
+fn color_multilines(color: Colour, s: &str) -> String {
+ collect_strings(s.split('\n').map(|i| color.paint(i))).join("\n")
+}
+
+#[derive(Debug)]
+pub struct ContextConfig<'a> {
+ pub context_size: usize,
+ pub skipping_marker: &'a str,
+}
+
+/// Container for line-by-line text diff result. Can be pretty-printed by Display trait.
+#[derive(Debug, PartialEq, Eq)]
+pub struct LineChangeset<'a> {
+ old: Vec<&'a str>,
+ new: Vec<&'a str>,
+
+ names: Option<(&'a str, &'a str)>,
+ diff_only: bool,
+ show_lines: bool,
+ trim_new_lines: bool,
+ aling_new_lines: bool,
+}
+
+impl<'a> LineChangeset<'a> {
+ pub fn new(old: Vec<&'a str>, new: Vec<&'a str>) -> LineChangeset<'a> {
+ LineChangeset {
+ old,
+ new,
+ names: None,
+ diff_only: false,
+ show_lines: true,
+ trim_new_lines: true,
+ aling_new_lines: false,
+ }
+ }
+
+ /// Sets names for side-by-side diff
+ pub fn names(mut self, old: &'a str, new: &'a str) -> Self {
+ self.names = Some((old, new));
+ self
+ }
+ /// Show only differences for side-by-side diff
+ pub fn set_diff_only(mut self, val: bool) -> Self {
+ self.diff_only = val;
+ self
+ }
+ /// Show lines in side-by-side diff
+ pub fn set_show_lines(mut self, val: bool) -> Self {
+ self.show_lines = val;
+ self
+ }
+ /// Trim new lines in side-by-side diff
+ pub fn set_trim_new_lines(mut self, val: bool) -> Self {
+ self.trim_new_lines = val;
+ self
+ }
+ /// Align new lines inside diff
+ pub fn set_align_new_lines(mut self, val: bool) -> Self {
+ self.aling_new_lines = val;
+ self
+ }
+ /// Returns Vec of changes
+ pub fn diff(&self) -> Vec<basic::DiffOp<'a, &str>> {
+ basic::diff(&self.old, &self.new)
+ }
+
+ #[cfg(feature = "prettytable-rs")]
+ fn prettytable_process(&self, a: &[&str], color: Option<Colour>) -> (String, usize) {
+ let mut start = 0;
+ let mut stop = a.len();
+ if self.trim_new_lines {
+ for (index, element) in a.iter().enumerate() {
+ if *element != "" {
+ break;
+ }
+ start = index + 1;
+ }
+ for (index, element) in a.iter().enumerate().rev() {
+ if *element != "" {
+ stop = index + 1;
+ break;
+ }
+ }
+ }
+ let out = &a[start..stop];
+ if let Some(color) = color {
+ (
+ collect_strings(out.iter().map(|i| color.paint(*i).to_string()))
+ .join("\n")
+ .replace("\t", " "),
+ start,
+ )
+ } else {
+ (out.join("\n").replace("\t", " "), start)
+ }
+ }
+
+ #[cfg(feature = "prettytable-rs")]
+ fn prettytable_process_replace(
+ &self,
+ old: &[&str],
+ new: &[&str],
+ ) -> ((String, String), (usize, usize)) {
+ let (old, old_offset) = self.prettytable_process(old, None);
+ let (new, new_offset) = self.prettytable_process(new, None);
+
+ let mut old_out = String::new();
+ let mut new_out = String::new();
+
+ for op in diff_words(&old, &new).diff() {
+ match op {
+ basic::DiffOp::Equal(a) => {
+ old_out.push_str(&a.join(""));
+ new_out.push_str(&a.join(""));
+ }
+ basic::DiffOp::Insert(a) => {
+ new_out.push_str(&color_multilines(Colour::Green, &a.join("")));
+ }
+ basic::DiffOp::Remove(a) => {
+ old_out.push_str(&color_multilines(Colour::Red, &a.join("")));
+ }
+ basic::DiffOp::Replace(a, b) => {
+ old_out.push_str(&color_multilines(Colour::Red, &a.join("")));
+ new_out.push_str(&color_multilines(Colour::Green, &b.join("")));
+ }
+ }
+ }
+
+ ((old_out, new_out), (old_offset, new_offset))
+ }
+
+ #[cfg(feature = "prettytable-rs")]
+ /// Prints side-by-side diff in table
+ pub fn prettytable(&self) {
+ let mut table = format_table::new();
+ if let Some((old, new)) = &self.names {
+ let mut header = vec![];
+ if self.show_lines {
+ header.push(Cell::new(""));
+ }
+ header.push(Cell::new(&Colour::Cyan.paint(old.to_string()).to_string()));
+ if self.show_lines {
+ header.push(Cell::new(""));
+ }
+ header.push(Cell::new(&Colour::Cyan.paint(new.to_string()).to_string()));
+ table.set_titles(Row::new(header));
+ }
+ let mut old_lines = 1;
+ let mut new_lines = 1;
+ let mut out: Vec<(usize, String, usize, String)> = Vec::new();
+ for op in &self.diff() {
+ match op {
+ basic::DiffOp::Equal(a) => {
+ let (old, offset) = self.prettytable_process(a, None);
+ if !self.diff_only {
+ out.push((old_lines + offset, old.clone(), new_lines + offset, old));
+ }
+ old_lines += a.len();
+ new_lines += a.len();
+ }
+ basic::DiffOp::Insert(a) => {
+ let (new, offset) = self.prettytable_process(a, Some(Colour::Green));
+ out.push((old_lines, "".to_string(), new_lines + offset, new));
+ new_lines += a.len();
+ }
+ basic::DiffOp::Remove(a) => {
+ let (old, offset) = self.prettytable_process(a, Some(Colour::Red));
+ out.push((old_lines + offset, old, new_lines, "".to_string()));
+ old_lines += a.len();
+ }
+ basic::DiffOp::Replace(a, b) => {
+ let ((old, new), (old_offset, new_offset)) =
+ self.prettytable_process_replace(a, b);
+ out.push((old_lines + old_offset, old, new_lines + new_offset, new));
+ old_lines += a.len();
+ new_lines += b.len();
+ }
+ };
+ }
+ for (old_lines, old, new_lines, new) in out {
+ if self.trim_new_lines && old.trim() == "" && new.trim() == "" {
+ continue;
+ }
+ if self.show_lines {
+ table.add_row(row![old_lines, old, new_lines, new]);
+ } else {
+ table.add_row(row![old, new]);
+ }
+ }
+ table.printstd();
+ }
+
+ fn remove_color(&self, a: &str) -> String {
+ Colour::Red.strikethrough().paint(a).to_string()
+ }
+
+ fn insert_color(&self, a: &str) -> String {
+ Colour::Green.paint(a).to_string()
+ }
+
+ /// Returns formatted string with colors
+ pub fn format(&self) -> String {
+ self.format_with_context(None, false)
+ }
+
+ /// Formats lines in DiffOp::Equal
+ fn format_equal(
+ &self,
+ lines: &[&str],
+ display_line_numbers: bool,
+ prefix_size: usize,
+ line_counter: &mut usize,
+ ) -> Option<String> {
+ lines
+ .iter()
+ .map(|line| {
+ let res = if display_line_numbers {
+ format!("{} ", *line_counter)
+ .pad_to_width_with_alignment(prefix_size, Alignment::Right)
+ + line
+ } else {
+ "".pad_to_width(prefix_size) + line
+ };
+ *line_counter += 1;
+ res
+ })
+ .reduce(|acc, line| acc + "\n" + &line)
+ }
+
+ /// Formats lines in DiffOp::Remove
+ fn format_remove(
+ &self,
+ lines: &[&str],
+ display_line_numbers: bool,
+ prefix_size: usize,
+ line_counter: &mut usize,
+ ) -> String {
+ lines
+ .iter()
+ .map(|line| {
+ let res = if display_line_numbers {
+ format!("{} ", *line_counter)
+ .pad_to_width_with_alignment(prefix_size, Alignment::Right)
+ + &self.remove_color(line)
+ } else {
+ "".pad_to_width(prefix_size) + &self.remove_color(line)
+ };
+ *line_counter += 1;
+ res
+ })
+ .reduce(|acc, line| acc + "\n" + &line)
+ .unwrap()
+ }
+
+ /// Formats lines in DiffOp::Insert
+ fn format_insert(&self, lines: &[&str], prefix_size: usize) -> String {
+ lines
+ .iter()
+ .map(|line| "".pad_to_width(prefix_size) + &self.insert_color(line))
+ .reduce(|acc, line| acc + "\n" + &line)
+ .unwrap()
+ }
+
+ /// Returns formatted string with colors.
+ /// May omit identical lines, if `context_size` is `Some(k)`.
+ /// In this case, only print identical lines if they are within `k` lines
+ /// of a changed line (as in `diff -C`).
+ pub fn format_with_context(
+ &self,
+ context_config: Option<ContextConfig>,
+ display_line_numbers: bool,
+ ) -> String {
+ let line_number_size = if display_line_numbers {
+ (self.old.len() as f64).log10().ceil() as usize
+ } else {
+ 0
+ };
+ let skipping_marker_size = if let Some(ContextConfig {
+ skipping_marker, ..
+ }) = context_config
+ {
+ skipping_marker.len()
+ } else {
+ 0
+ };
+ let prefix_size = max(line_number_size, skipping_marker_size) + 1;
+
+ let mut next_line = 1;
+
+ let mut diff = self.diff().into_iter().peekable();
+ let mut out: Vec<String> = Vec::with_capacity(diff.len());
+ let mut at_beginning = true;
+ while let Some(op) = diff.next() {
+ match op {
+ basic::DiffOp::Equal(a) => match context_config {
+ None => out.push(a.join("\n")),
+ Some(ContextConfig {
+ context_size,
+ skipping_marker,
+ }) => {
+ let mut lines = a;
+ if !at_beginning {
+ let upper_bound = min(context_size, lines.len());
+ if let Some(newlines) = self.format_equal(
+ &lines[..upper_bound],
+ display_line_numbers,
+ prefix_size,
+ &mut next_line,
+ ) {
+ out.push(newlines)
+ }
+ lines = &lines[upper_bound..];
+ }
+ if lines.len() == 0 {
+ continue;
+ }
+ let lower_bound = if lines.len() > context_size {
+ lines.len() - context_size
+ } else {
+ 0
+ };
+ if lower_bound > 0 {
+ out.push(skipping_marker.to_string());
+ next_line += lower_bound
+ }
+ if diff.peek().is_none() {
+ continue;
+ }
+ if let Some(newlines) = self.format_equal(
+ &lines[lower_bound..],
+ display_line_numbers,
+ prefix_size,
+ &mut next_line,
+ ) {
+ out.push(newlines)
+ }
+ }
+ },
+ basic::DiffOp::Insert(a) => out.push(self.format_insert(a, prefix_size)),
+ basic::DiffOp::Remove(a) => out.push(self.format_remove(
+ a,
+ display_line_numbers,
+ prefix_size,
+ &mut next_line,
+ )),
+ basic::DiffOp::Replace(a, b) => {
+ out.push(self.format_remove(
+ a,
+ display_line_numbers,
+ prefix_size,
+ &mut next_line,
+ ));
+ out.push(self.format_insert(b, prefix_size));
+ }
+ }
+ at_beginning = false;
+ }
+ out.join("\n")
+ }
+}
+
+impl<'a> fmt::Display for LineChangeset<'a> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ write!(formatter, "{}", self.format())
+ }
+}
+
+pub fn diff_lines<'a>(old: &'a str, new: &'a str) -> LineChangeset<'a> {
+ let old: Vec<&str> = old.lines().collect();
+ let new: Vec<&str> = new.lines().collect();
+
+ LineChangeset::new(old, new)
+}
+
+fn _test_splitter_basic(text: &str, exp: &[&str]) {
+ let res = collect_strings(
+ split_by_char_fn(&text, |c: char| c.is_whitespace()).map(|s| s.to_string()),
+ );
+ assert_eq!(res, exp)
+}
+
+#[test]
+fn test_splitter() {
+ _test_splitter_basic(
+ " blah test2 test3 ",
+ &[" ", " ", "blah", " ", "test2", " ", "test3", " ", " "],
+ );
+ _test_splitter_basic(
+ "\tblah test2 test3 ",
+ &["\t", "blah", " ", "test2", " ", "test3", " ", " "],
+ );
+ _test_splitter_basic(
+ "\tblah test2 test3 t",
+ &["\t", "blah", " ", "test2", " ", "test3", " ", " ", "t"],
+ );
+ _test_splitter_basic(
+ "\tblah test2 test3 tt",
+ &["\t", "blah", " ", "test2", " ", "test3", " ", " ", "tt"],
+ );
+}
+
+#[test]
+fn test_basic() {
+ println!("diff_chars: {}", diff_chars("abefcd", "zadqwc"));
+ println!(
+ "diff_chars: {}",
+ diff_chars(
+ "The quick brown fox jumps over the lazy dog",
+ "The quick brown dog leaps over the lazy cat"
+ )
+ );
+ println!(
+ "diff_chars: {}",
+ diff_chars(
+ "The red brown fox jumped over the rolling log",
+ "The brown spotted fox leaped over the rolling log"
+ )
+ );
+ println!(
+ "diff_chars: {}",
+ diff_chars(
+ "The red brown fox jumped over the rolling log",
+ "The brown spotted fox leaped over the rolling log"
+ )
+ .set_highlight_whitespace(true)
+ );
+ println!(
+ "diff_words: {}",
+ diff_words(
+ "The red brown fox jumped over the rolling log",
+ "The brown spotted fox leaped over the rolling log"
+ )
+ );
+ println!(
+ "diff_words: {}",
+ diff_words(
+ "The quick brown fox jumps over the lazy dog",
+ "The quick, brown dog leaps over the lazy cat"
+ )
+ );
+}
+
+#[test]
+fn test_split_words() {
+ assert_eq!(
+ collect_strings(split_words("Hello World")),
+ ["Hello", " ", "World"]
+ );
+ assert_eq!(
+ collect_strings(split_words("Hello😋World")),
+ ["Hello", "😋", "World"]
+ );
+ assert_eq!(
+ collect_strings(split_words(
+ "The red brown fox\tjumped, over the rolling log"
+ )),
+ [
+ "The", " ", "red", " ", "brown", " ", "fox", "\t", "jumped", ",", " ", "over", " ",
+ "the", " ", "rolling", " ", "log"
+ ]
+ );
+}
+
+#[test]
+fn test_diff_lines() {
+ let code1_a = r#"
+void func1() {
+ x += 1
+}
+
+void func2() {
+ x += 2
+}
+ "#;
+ let code1_b = r#"
+void func1(a: u32) {
+ x += 1
+}
+
+void functhreehalves() {
+ x += 1.5
+}
+
+void func2() {
+ x += 2
+}
+
+void func3(){}
+"#;
+ println!("diff_lines:");
+ println!("{}", diff_lines(code1_a, code1_b));
+ println!("====");
+ diff_lines(code1_a, code1_b)
+ .names("left", "right")
+ .set_align_new_lines(true)
+ .prettytable();
+}
+
+fn _test_colors(changeset: &InlineChangeset, exp: &[(Option<Style>, &str)]) {
+ let color_s: String = collect_strings(exp.iter().map(|(style_opt, s)| {
+ if let Some(style) = style_opt {
+ style.paint(s.to_string()).to_string()
+ } else {
+ s.to_string()
+ }
+ }))
+ .join("");
+ assert_eq!(format!("{}", changeset), color_s);
+}
+
+#[test]
+fn test_diff_words_issue_1() {
+ let insert_style = Colour::Green.normal();
+ let insert_whitespace_style = Colour::White.on(Colour::Green);
+ let remove_style = Colour::Red.strikethrough();
+ let remove_whitespace_style = Colour::White.on(Colour::Red);
+ let d1 = diff_words(
+ "und meine Unschuld beweisen!",
+ "und ich werde meine Unschuld beweisen!",
+ )
+ .set_insert_style(insert_style)
+ .set_insert_whitespace_style(insert_whitespace_style)
+ .set_remove_style(remove_style)
+ .set_remove_whitespace_style(remove_whitespace_style);
+
+ println!("diff_words: {} {:?}", d1, d1.diff());
+
+ _test_colors(
+ &d1,
+ &[
+ (None, "und "),
+ (Some(insert_style), "ich"),
+ (Some(insert_whitespace_style), " "),
+ (Some(insert_style), "werde"),
+ (Some(insert_whitespace_style), " "),
+ (None, "meine Unschuld beweisen!"),
+ ],
+ );
+ _test_colors(
+ &d1.set_highlight_whitespace(false),
+ &[
+ (None, "und "),
+ (Some(insert_style), "ich werde "),
+ (None, "meine Unschuld beweisen!"),
+ ],
+ );
+ let d2 = diff_words(
+ "Campaignings aus dem Ausland gegen meine Person ausfindig",
+ "Campaignings ausfindig",
+ );
+ println!("diff_words: {} {:?}", d2, d2.diff());
+ _test_colors(
+ &d2,
+ &[
+ (None, "Campaignings "),
+ (Some(remove_style), "aus"),
+ (Some(remove_whitespace_style), " "),
+ (Some(remove_style), "dem"),
+ (Some(remove_whitespace_style), " "),
+ (Some(remove_style), "Ausland"),
+ (Some(remove_whitespace_style), " "),
+ (Some(remove_style), "gegen"),
+ (Some(remove_whitespace_style), " "),
+ (Some(remove_style), "meine"),
+ (Some(remove_whitespace_style), " "),
+ (Some(remove_style), "Person"),
+ (Some(remove_whitespace_style), " "),
+ (None, "ausfindig"),
+ ],
+ );
+ let d3 = diff_words("des kriminellen Videos", "des kriminell erstellten Videos");
+ println!("diff_words: {} {:?}", d3, d3.diff());
+ _test_colors(
+ &d3,
+ &[
+ (None, "des "),
+ (Some(remove_style), "kriminellen"),
+ (Some(insert_style), "kriminell"),
+ (None, " "),
+ (Some(insert_style), "erstellten"),
+ (Some(insert_whitespace_style), " "),
+ (None, "Videos"),
+ ],
+ );
+}
+
+#[test]
+fn test_prettytable_process() {
+ let d1 = diff_lines(
+ r#"line1
+ line2
+ line3
+ "#,
+ r#"line1
+ line2
+ line2.5
+ line3
+ "#,
+ );
+
+ println!("diff_lines: {} {:?}", d1, d1.diff());
+ assert_eq!(d1.prettytable_process(&["a", "b", "c"], None), (String::from("a\nb\nc"), 0));
+ assert_eq!(d1.prettytable_process(&["a", "b", "c", ""], None), (String::from("a\nb\nc"), 0));
+ assert_eq!(d1.prettytable_process(&["", "a", "b", "c"], None), (String::from("a\nb\nc"), 1));
+ assert_eq!(d1.prettytable_process(&["", "a", "b", "c", ""], None), (String::from("a\nb\nc"), 1));
+}
+
+#[test]
+fn test_format_with_context() {
+ let d = diff_lines(
+ r#"line1
+ line2
+ line3
+ line4
+ line5
+ line6
+ line7
+ line8
+ line9
+ line10
+ line11
+ line12"#,
+ r#"line1
+ line2
+ line4
+ line5
+ line6.5
+ line7
+ line8
+ line9
+ line10
+ line11.5
+ line12"#,
+ );
+ let context = |n| ContextConfig {
+ context_size: n,
+ skipping_marker: "...",
+ };
+ println!(
+ "diff_lines:\n{}\n{:?}",
+ d.format_with_context(Some(context(0)), true),
+ d.diff()
+ );
+ let formatted_none = d.format_with_context(None, true);
+ let formatted_some_0 = d.format_with_context(Some(context(0)), true);
+ let formatted_some_1 = d.format_with_context(Some(context(1)), true);
+ let formatted_some_2 = d.format_with_context(Some(context(2)), true);
+ // With a context of size 2, every line is present
+ assert_eq!(
+ formatted_none.lines().count(),
+ formatted_some_2.lines().count()
+ );
+ // with a context of size 1:
+ // * line 1 is replaced by '...' (-0 lines)
+ // * line 8-9 are replaced by '...' (-1 line)
+ assert_eq!(
+ formatted_none.lines().count() - 1,
+ formatted_some_1.lines().count()
+ );
+ // with a context of size 0:
+ // * lines 1-2 are replaced by '...' (-1 line)
+ // * lines 4-5 are replaced by '...' (-1 line)
+ // * lines 7-10 are replaced by '...' (-3 lines)
+ // * line 12 is replaced by '...' (-0 lines)
+ assert_eq!(
+ formatted_none.lines().count() - 5,
+ formatted_some_0.lines().count()
+ );
+}