use std::io::Write;
use super::{diff_algorithm::Algorithm, draw_diff::DrawDiff, themes::Theme};
pub fn diff(w: &mut dyn Write, old: &str, new: &str, theme: &dyn Theme) -> std::io::Result<()> {
if !Algorithm::has_available_algorithms() {
return write!(
w,
"Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
);
}
let output: DrawDiff<'_> = DrawDiff::new(old, new, theme);
write!(w, "{output}")
}
pub fn diff_with_algorithm(
w: &mut dyn Write,
old: &str,
new: &str,
theme: &dyn Theme,
algorithm: Algorithm,
) -> std::io::Result<()> {
if !Algorithm::has_available_algorithms() {
return write!(
w,
"Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
);
}
let available_algorithms = Algorithm::available_algorithms();
if !available_algorithms.contains(&algorithm) {
if let Some(available_algo) = Algorithm::first_available() {
let output: DrawDiff<'_> = DrawDiff::with_algorithm(old, new, theme, available_algo);
return write!(w, "{output}");
}
return write!(
w,
"Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
);
}
let output: DrawDiff<'_> = DrawDiff::with_algorithm(old, new, theme, algorithm);
write!(w, "{output}")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::themes::{ArrowsTheme, SignsTheme};
use std::io::{Cursor, Write};
#[test]
fn test_diff_with_arrows_theme() {
let old = "The quick brown fox";
let new = "The quick red fox";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
assert!(output.contains("< left / > right"));
}
#[test]
fn test_diff_with_signs_theme() {
let old = "The quick brown fox";
let new = "The quick red fox";
let mut buffer = Cursor::new(Vec::new());
let theme = SignsTheme::default();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("-The quick brown fox"));
assert!(output.contains("+The quick red fox"));
assert!(output.contains("--- remove | insert +++"));
}
#[test]
fn test_diff_empty_inputs() {
let old = "";
let new = "";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert_eq!(output, "< left / > right\n");
}
#[test]
fn test_diff_identical_inputs() {
let text = "same text";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, text, text, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("< left / > right"));
assert!(output.contains(" same text"));
assert!(!output.contains("<same text"));
assert!(!output.contains(">same text"));
}
#[test]
fn test_diff_multiline() {
let old = "line 1\nline 2\nline 3";
let new = "line 1\nmodified line 2\nline 3";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains(" line 1\n"));
assert!(output.contains("<line 2\n"));
assert!(output.contains(">modified line 2\n"));
assert!(output.contains(" line 3"));
}
#[test]
fn test_diff_trailing_newline() {
let old = "line\n";
let new = "line";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("line␊"));
}
#[test]
fn test_diff_with_custom_theme() {
use std::borrow::Cow;
#[derive(Debug)]
struct CustomTheme;
impl Theme for CustomTheme {
fn equal_prefix<'this>(&self) -> Cow<'this, str> {
"=".into()
}
fn delete_prefix<'this>(&self) -> Cow<'this, str> {
"-".into()
}
fn insert_prefix<'this>(&self) -> Cow<'this, str> {
"+".into()
}
fn header<'this>(&self) -> Cow<'this, str> {
"CUSTOM HEADER\n".into()
}
}
let old = "old";
let new = "new";
let mut buffer = Cursor::new(Vec::new());
let theme = CustomTheme;
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("CUSTOM HEADER"));
assert!(output.contains("-old"));
assert!(output.contains("+new"));
}
#[test]
fn test_diff_writer_error() {
struct ErrorWriter;
impl Write for ErrorWriter {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("Test error"))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let old = "old";
let new = "new";
let mut writer = ErrorWriter;
let theme = ArrowsTheme::default();
let result = diff(&mut writer, old, new, &theme);
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.kind(), std::io::ErrorKind::Other);
assert_eq!(error.to_string(), "Test error");
}
#[test]
fn test_diff_with_algorithm_no_algorithms_available() {
let old = "old";
let new = "new";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
let mut test_buffer = Cursor::new(Vec::new());
if !Algorithm::has_available_algorithms() {
write!(
&mut test_buffer,
"Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
).unwrap();
}
let mut mock_buffer = Cursor::new(Vec::new());
let mock_no_algorithms = true;
if mock_no_algorithms {
write!(
&mut mock_buffer,
"Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
).unwrap();
}
let mock_output = String::from_utf8(mock_buffer.into_inner()).expect("Not valid UTF-8");
assert!(
mock_output.contains("Error: No diff algorithms are available"),
"Error message should be shown when no algorithms are available"
);
let result = diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers);
assert!(result.is_ok());
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
if Algorithm::has_available_algorithms() {
assert!(
!output.contains("Error: No diff algorithms are available"),
"Should not show error when algorithms are available"
);
} else {
assert!(
output.contains("Error: No diff algorithms are available"),
"Should show error when no algorithms are available"
);
}
}
#[test]
fn test_diff_large_inputs() {
let old = "a\n".repeat(1000);
let new = "a\n".repeat(500) + &"b\n".repeat(500);
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, &old, &new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
let line_count = output.lines().count();
assert_eq!(line_count, 1 + 500 + 500 + 500);
assert!(output.contains(" a")); assert!(output.contains("<a")); assert!(output.contains(">b")); }
#[test]
#[cfg(all(feature = "myers", not(feature = "similar")))]
fn test_only_myers_algorithm() {
let old = "The quick brown fox";
let new = "The quick red fox";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
let mut buffer = Cursor::new(Vec::new());
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Similar).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
}
#[test]
#[cfg(all(feature = "similar", not(feature = "myers")))]
fn test_only_similar_algorithm() {
let old = "The quick brown fox";
let new = "The quick red fox";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Similar).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
let mut buffer = Cursor::new(Vec::new());
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
}
#[test]
#[cfg(not(any(feature = "myers", feature = "similar")))]
fn test_no_algorithms_available() {
let old = "The quick brown fox";
let new = "The quick red fox";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("Error: No diff algorithms are available"));
let mut buffer = Cursor::new(Vec::new());
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(output.contains("Error: No diff algorithms are available"));
}
#[test]
fn test_diff_with_algorithm_unavailable() {
let old = "old";
let new = "new";
let mut buffer = Cursor::new(Vec::new());
let theme = ArrowsTheme::default();
if !Algorithm::has_available_algorithms() {
return;
}
let available_algorithms = Algorithm::available_algorithms();
let unavailable_algorithm = if available_algorithms.contains(&Algorithm::Myers)
&& !available_algorithms.contains(&Algorithm::Similar)
{
Algorithm::Similar
} else if !available_algorithms.contains(&Algorithm::Myers)
&& available_algorithms.contains(&Algorithm::Similar)
{
Algorithm::Myers
} else {
return;
};
diff_with_algorithm(&mut buffer, old, new, &theme, unavailable_algorithm).unwrap();
let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
assert!(
!output.contains("Error: No diff algorithms are available"),
"Should use an available algorithm instead of showing an error"
);
assert!(
output.contains("old") || output.contains("new"),
"Output should contain diff content"
);
}
}